视觉基础篇14 # 如何使用片元着色器进行几何造型?

Posted 凯小默

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了视觉基础篇14 # 如何使用片元着色器进行几何造型?相关的知识,希望对你有一定的参考价值。

说明

【跟月影学可视化】学习笔记。

如何用片元着色器控制局部颜色?

把图片绘制为纯黑色:

const fragment = `
    #ifdef GL_ES
    precision highp float;
    #endif

    varying vec2 vUv;

    void main() 
        gl_FragColor = vec4(0, 0, 0, 1);
    
`;


根据纹理坐标值来绘制,让某个图案的颜色,从左到右由黑向白过渡

const fragment = `
    #ifdef GL_ES
    precision highp float;
    #endif

    varying vec2 vUv;

    void main() 
        gl_FragColor.rgb = vec3(vUv.x);
        gl_FragColor.a = 1.0;
    
`;


使用乘法创造一个 10*10 的方格,让每个格子左上角是绿色,右下角是红色,中间是过渡色。

const fragment = `
    #ifdef GL_ES
    precision highp float;
    #endif

    varying vec2 vUv;

    void main() 
        vec2 st = vUv * 10.0;
        gl_FragColor.rgb = vec3(fract(st), 0.0);
        gl_FragColor.a = 1.0;
    
`;

通过 idx = floor(st) 获取网格的索引,判断网格索引除以 2 的余数(奇偶性),根据它来决定是否翻转网格内的 x、y 坐标。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>如何用片元着色器控制局部颜色?</title>
        <style>
            canvas 
                border: 1px dashed salmon;
            
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;

                varying vec2 vUv;

                void main() 
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                
            `;

            // // 把图片绘制为纯黑色
            // const fragment = `
            //     #ifdef GL_ES
            //     precision highp float;
            //     #endif

            //     varying vec2 vUv;

            //     void main() 
            //         gl_FragColor = vec4(0, 0, 0, 1);
            //     
            // `;
            
            // // 根据纹理坐标值来绘制,让某个图案的颜色,从左到右由黑向白过渡
            // const fragment = `
            //     #ifdef GL_ES
            //     precision highp float;
            //     #endif

            //     varying vec2 vUv;

            //     void main() 
            //         gl_FragColor.rgb = vec3(vUv.x);
            //         gl_FragColor.a = 1.0;
            //     
            // `;

            // // 使用乘法创造一个 10*10 的方格,让每个格子左上角是绿色,右下角是红色,中间是过渡色。
            // const fragment = `
            //     #ifdef GL_ES
            //     precision highp float;
            //     #endif

            //     varying vec2 vUv;

            //     void main() 
            //         vec2 st = vUv * 10.0;
            //         gl_FragColor.rgb = vec3(fract(st), 0.0);
            //         gl_FragColor.a = 1.0;
            //     
            // `;

            // 通过 idx = floor(st) 获取网格的索引,判断网格索引除以 2 的余数(奇偶性),根据它来决定是否翻转网格内的 x、y 坐标。
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                varying vec2 vUv;

                void main() 
                    vec2 st = vUv * 10.0;
                    vec2 idx = floor(st);
                    vec2 grid = fract(st);

                    vec2 t = mod(idx, 2.0);
                    
                    if(t.x == 1.0) 
                        grid.x = 1.0 - grid.x;
                    
                    if(t.y == 1.0) 
                        grid.y = 1.0 - grid.y;
                    
                    gl_FragColor.rgb = vec3(grid, 0.0);
                    gl_FragColor.a = 1.0;
                
            `;

            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);

            // 加载片元着色器并创建程序
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            // 将顶点数据送入缓冲区
            renderer.setMeshData([
                
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: 
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    ,
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                ,
            ]);
            
            // 渲染
            renderer.render();
        </script>
    </body>
</html>

如何用片元着色器绘制圆、线段和几何图形

绘制圆

绘制一个模糊的圆

const fragment = `
	#ifdef GL_ES
	precision highp float;
	#endif
	
	varying vec2 vUv;
	
	void main() 
		floatd = distance(vUv, vec2(0.5));
		gl_FragColor.rgb = d * vec3(1.0);
		gl_FragColor.a = 1.0;
	
`;


绘制一个清晰的圆

const fragment = `
    #ifdef GL_ES
    precision highp float;
    #endif

    varying vec2 vUv;

    void main() 
        float d = distance(vUv, vec2(0.5));
        gl_FragColor.rgb = step(d, 0.2) * vec3(1.0);
        gl_FragColor.a = 1.0;
    
`;


因为浮点数计算的精度导致的锯齿现象。用 smoothstep 代替 step 即可解决这种问题。smoothstep 在 step-start 和 step-end 之间有一个平滑过渡的区间。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>片元着色器绘制圆</title>
        <style>
            canvas 
                border: 1px dashed salmon;
            
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;

                varying vec2 vUv;

                void main() 
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                
            `;
            // // 模糊的圆
            // const fragment = `
            //     #ifdef GL_ES
            //     precision highp float;
            //     #endif

            //     varying vec2 vUv;

            //     void main() 
            //         float d = distance(vUv, vec2(0.5));
            //         gl_FragColor.rgb = d * vec3(1.0);
            //         gl_FragColor.a = 1.0;
            //     
            // `;

            // // 清晰的圆
            // const fragment = `
            //     #ifdef GL_ES
            //     precision highp float;
            //     #endif

            //     varying vec2 vUv;

            //     void main() 
            //         float d = distance(vUv, vec2(0.5));
            //         gl_FragColor.rgb = step(d, 0.2) * vec3(1.0);
            //         gl_FragColor.a = 1.0;
            //     
            // `;
            // 清晰的圆无锯齿
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                varying vec2 vUv;

                void main() 
                    float d = distance(vUv, vec2(0.5));
                    gl_FragColor.rgb = smoothstep(d, d + 0.01, 0.2) * vec3(1.0);
                    gl_FragColor.a = 1.0;
                
            `;

            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);

            // 加载片元着色器并创建程序
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            // 将顶点数据送入缓冲区
            renderer.setMeshData([
                
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: 
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    ,
                    cells: [
                        [0, 1, 2],
                        [2, 0, 3],
                    ],
                ,
            ]);
            
            // 渲染
            renderer.render();
        </script>
    </body>
</html>

实现图片的渐显渐隐效果

上一节我们实现了图片粒子化,下面利用绘制圆实现图片的渐显渐隐效果

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>片元着色器绘制圆实现图片的渐显渐隐效果</title>
        <style>
            canvas 
                border: 1px dashed salmon;
            
        </style>
    </head>
    <body>
        <canvas width="1920" height="1080"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;

                varying vec2 vUv;

                void main() 
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                
            `;

            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                uniform sampler2D tMap;
                uniform vec2 uResolution;
                uniform float uTime;
                varying vec2 vUv;

                float random (vec2 st) 
                    return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123);
                

                void main() 
                    vec2 uv = vUv;
                    uv.y *= uResolution.y / uResolution.x;
                    vec2 st = uv * 100.0;
                    float d = distance(fract(st), vec2(0.5));
                    float p = uTime + random(floor(st));
                    float shading = 0.5 + 0.5 * sin(p);
                    d = smoothstep(d, d + 0.01, 1.0 * shading);
                    vec4 color = texture2D(tMap, vUv);
                    gl_FragColor.rgb = color.rgb * clamp(0.5, 1.3, d + 1.0 * shading);
                    gl_FragColor.a = color.a;
                
            `;

            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);

            // 加载片元着色器并创建程序
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            (async function () 
                const texture = await renderer.loadTexture('./assets/img/flower.jpg');
                renderer.uniforms.tMap = texture;
                renderer.uniforms.uResolution = [canvas.width, canvas.height];
                renderer.uniforms.uTime = 0;
                // 将顶点数据送入缓冲区
                renderer.setMeshData([
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: 
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1],
                            [1, 0],
                        ],
                    ,
                    cells: [[0, 1, 2], [2, 0, 3]],
                ]);

                renderer.render();

                function update(t) 
                    renderer.uniforms.uTime = t / 500;
                    requestAnimationFrame(update);
                

                update(0);
            ());
        </script>
    </body>
</html>

绘制线

计算点到直线(向量)的距离即可。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>片元着色器绘制圆</title>
        <style>
            canvas 
                border: 1px dashed salmon;
            
        </style>
    </head>
    <body>
        <canvas width="512" height="512"></canvas>
        <script src="./common/lib/gl-renderer.js"></script>
        <script>
            const vertex = `
                attribute vec2 a_vertexPosition;
                attribute vec2 uv;

                varying vec2 vUv;

                void main() 
                    gl_PointSize = 1.0;
                    vUv = uv;
                    gl_Position = vec4(a_vertexPosition, 1, 1);
                
            `;
            // 画出一条斜线
            const fragment = `
                #ifdef GL_ES
                precision highp float;
                #endif

                varying vec2 vUv;

                void main() 
                    vec3 line = vec3(1, 1, 0);
                    float d = abs(cross(vec3(vUv,0), normalize(line)).z); 
                    gl_FragColor.rgb = (1.0 - smoothstep(0.0, 0.01, d)) * vec3(1.0);
                    gl_FragColor.a = 1.0;
                
            `;

            const canvas = document.querySelector("canvas");
            const renderer = new GlRenderer(canvas);

            // 加载片元着色器并创建程序
            const program = renderer.compileSync(fragment, vertex);
            renderer.useProgram(program);

            // 将顶点数据送入缓冲区
            renderer.setMeshData([
                
                    positions: [
                        [-1, -1],
                        [-1, 1],
                        [1, 1],
                        [1, -1],
                    ],
                    attributes: 
                        uv: [
                            [0, 0],
                            [0, 1],
                            [1, 1以上是关于视觉基础篇14 # 如何使用片元着色器进行几何造型?的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL-探路篇

OpenGL渲染管线(rendering pipeline)

视觉基础篇17 # 如何使用后期处理通道增强图像效果?

UnityShader[4]几何着色器与可交互草地

Linux OpenGL 实践篇-13-geometryshader

WebGL学习系列-片元着色器简介