Drawing a circle with shaders

I am doing my first steps with threejs and I am trying to experiment with shaders. I meant to draw one single circle at the center of the screen but I fail to do it and cannot understand why.

The shader code is taken from the book of shaders. What am I missing? Could you help please

<!DOCTYPE html>
<html>
<head>
    <title>Title</title>

    <script src='three.js/build/three.js'></script>
    <script src='three.js/examples/js/controls/OrbitControls.js'></script>
    <script src='three.js/examples/js/controls/PointerLockControls.js'></script>

</head>
<body>


<canvas class="webgl"></canvas>

<script>

    const canvas = document.querySelector('canvas.webgl')
    const scene = new THREE.Scene()
    const textureLoader = new THREE.TextureLoader()
    const geometry = new THREE.PlaneBufferGeometry(1, 1, 32, 32)
    const material = new THREE.RawShaderMaterial({
            vertexShader: `
        uniform mat4 projectionMatrix;
        uniform mat4 viewMatrix;
        uniform mat4 modelMatrix;

        attribute vec3 position;

        void main()
        {
            gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
        }
    `,
    fragmentShader: `
        precision mediump float;
        uniform vec2 u_resolution;

        float circle(in vec2 _st, in float _radius){
        vec2 dist = _st-vec2(0.5);
        return 1.-smoothstep(_radius-(_radius*0.01),
                         _radius+(_radius*0.01),
                         dot(dist,dist)*4.0);
        }

        void main()
        {
            vec2 st = gl_FragCoord.xy/u_resolution.xy;
            vec3 color = vec3(circle(st,0.9));
            gl_FragColor = vec4( color, 1.0 );
        }
    `
    })

    const mesh = new THREE.Mesh(geometry, material)
    scene.add(mesh)

    window.addEventListener('resize', () => {
        // Update camera
        camera.aspect =window.innerWidth / window.innerHeight
        camera.updateProjectionMatrix()

        // Update renderer
        renderer.setSize(window.innerWidth, window.innerHeight)
        renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
    })

    const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight, 0.1, 100)
    camera.position.set(0, 0, 10)
    scene.add(camera)

    const controls = new THREE.OrbitControls(camera, canvas)
    controls.enableDamping = true

    const renderer = new THREE.WebGLRenderer({
        canvas: canvas
    })
    renderer.setSize(window.innerWidth, window.innerHeight)
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

    const clock = new THREE.Clock()

    const tick = () => {
        const elapsedTime = clock.getElapsedTime()
        controls.update()
        renderer.render(scene, camera)
        window.requestAnimationFrame(tick)
    }

    tick()
</script>


</body>
</html>

You don’t pass the data in this uniform. So you get wrong result in this line:
vec2 st = gl_FragCoord.xy/u_resolution.xy;

Just an option, not the ultimate solution: Edit fiddle - JSFiddle - Code Playground

Thanks so much! I cant do the very basics. Any idea why this doesnt work? I tried to pass a uniform but obviously I got it wrong.

    const material = new THREE.RawShaderMaterial({
            vertexShader: `
        uniform mat4 projectionMatrix;
        uniform mat4 viewMatrix;
        uniform mat4 modelMatrix;

        attribute vec3 position;

        void main()
        {
            gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
        }
    `,
    fragmentShader: `
        precision mediump float;
        uniform vec2 u_resolution;

        float circle(in vec2 _st, in float _radius){
        vec2 dist = _st-vec2(0.5);
        return 1.-smoothstep(_radius-(_radius*0.01),
                         _radius+(_radius*0.01),
                         dot(dist,dist)*4.0);
        }

        void main()
        {
            vec2 st = gl_FragCoord.xy/u_resolution.xy;
            vec3 color = vec3(circle(st,0.9));
            gl_FragColor = vec4( color, 1.0 );
        }
    `,
        uniforms: {u_resolution: new THREE.Vector2(20, 20) }
    })

Maybe, because you need to pass the size of your window?

  uniforms: {
    u_resolution: {
      value: new THREE.Vector2(innerWidth, innerHeight)
    }
  }

Ah! And the object of a uniform has to have value property.

Still no luck. Passing uniforms: {u_resolution: new THREE.Vector2(window.innerWidth, window.innerHeight) } gives me the same error:
Uncaught TypeError: Cannot read property 'x' of undefined

Totally removing any reference to u_resolution from inside the fragmentShader gives me just a black screen and no error in the console

You’re still missing that value property.

Oh! yes! Thanks a million, I must be blind! Thanks so much for helping out

related:

from the Collection of examples from discourse.threejs.org

2019
2021-07-21 09.59.52

2021-07-21 10.01.23

2020

2021

1 Like