一、  序言

最近shadertoy看到有人把透视转换、光照、3D转2D光栅化转移到了fragShader实现,虽然感觉没啥实用性,过于麻烦,但觉得非常厉害又有趣,让我感觉看到了新鲜事物一样。拜读之后代码一点个人理解一个笔记记录一下

体验地址Shader - Shadertoy BETA

二、  立方体的建立

这是整个shader代码中最简单的部分:

2.1、声明立方体坐标:

对应实际位置假设

2.2、 三角面的建立

立方体每个面需要两个三角形构成每个三角形需要三个顶点每个顶点一个三维向量表达同时由于每个面需要需要进行贴图因此需要三个纹理采样二维坐标对应3采样位置然后因为需要实现光照需要添加一个3维法向量。

具体数据结构如下

2.3、 立方体三角形数组赋值,没啥好解释的:

三、  投影变换、光栅化:

看看mainImage主过程这部分才是复杂部分

3.1、计算世界坐标

这一步VertexShader或者应用层本身该干的过来哈哈

3.2 坐标重整:

由于fragshader传入fragCoord坐标左上角(0,0)右下角(1,1),因此要模拟OpenGL本身0,0在中心、最小值为-1、最大值为1的坐标系,必须先把值乘以2,再减去最大的x、y值,除以iResolution.y像素宽度量化为统一的单位区间值。

3.3、 采样和栅格化

过程概括就是,遍历每个三角形三角形经过投影变换坐标转换纹理采样坐标从而三角形相对位置纹理映射三角面中,根据向量位置的点积值决定最后的色深,最后输出当前正在处理片源

实际上,如果只是想使用fragshader实现一个旋转的立方体,代码还可以精简一下:

// Created by inigo quilez - iq/2013
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

// a perspective correct triangle rasterizer, in a shader!! :D

mat4 setRotation( float x, float y, float z )
{
    float a = sin(x); float b = cos(x); 
    float c = sin(y); float d = cos(y); 
    float e = sin(z); float f = cos(z); 

    float ac = a*c;
    float bc = b*c;

    return mat4( d*f,      d*e,       -c, 0.0,
                 ac*f-b*e, ac*e+b*f, a*d, 0.0,
                 bc*f+a*e, bc*e-a*f, b*d, 0.0,
                 0.0,      0.0,      0.0, 1.0 );
}

mat4 setTranslation( float x, float y, float z )
{
    return mat4( 1.0, 0.0, 0.0, 0.0,
				 0.0, 1.0, 0.0, 0.0,
				 0.0, 0.0, 1.0, 0.0,
				 x,     y,   z, 1.0 );
}

struct Triangle
{
    vec3 a; vec2 aUV;
    vec3 b; vec2 bUV;
    vec3 c; vec2 cUV;
    vec3 n;
};

const vec3 vertsCube[8]  = vec3[8]( vec3( -1.0, -1.0, -1.0 ),
                                    vec3( -1.0, -1.0,  1.0 ),
                                    vec3( -1.0,  1.0, -1.0 ),
                                    vec3( -1.0,  1.0,  1.0 ),
                                    vec3(  1.0, -1.0, -1.0 ),
                                    vec3(  1.0, -1.0,  1.0 ),
                                    vec3(  1.0,  1.0, -1.0 ),
                                    vec3(  1.0,  1.0,  1.0 ) );

Triangle triangles[12];

void createCube( void )
{
    triangles[0].a = vertsCube[1]; triangles[0].aUV = vec2(0.0,0.0);
    triangles[0].b = vertsCube[5]; triangles[0].bUV = vec2(1.0,0.0);
    triangles[0].c = vertsCube[7]; triangles[0].cUV = vec2(1.0,1.0);
    triangles[0].n = vec3( 0.0, 0.0, 1.0 );
    triangles[1].a = vertsCube[1]; triangles[1].aUV = vec2(0.0,0.0),
    triangles[1].b = vertsCube[7]; triangles[1].bUV = vec2(1.0,1.0),
    triangles[1].c = vertsCube[3]; triangles[1].cUV = vec2(0.0,1.0),
    triangles[1].n = vec3( 0.0, 0.0, 1.0 );

    triangles[2].a = vertsCube[5]; triangles[2].aUV = vec2(0.0,0.0);
    triangles[2].b = vertsCube[4]; triangles[2].bUV = vec2(1.0,0.0);
    triangles[2].c = vertsCube[6]; triangles[2].cUV = vec2(1.0,1.0);
    triangles[2].n = vec3( 1.0, 0.0, 0.0 );
    triangles[3].a = vertsCube[5]; triangles[3].aUV = vec2(0.0,0.0);
    triangles[3].b = vertsCube[6]; triangles[3].bUV = vec2(1.0,1.0);
    triangles[3].c = vertsCube[7]; triangles[3].cUV = vec2(0.0,1.0);
    triangles[3].n = vec3( 1.0, 0.0, 0.0 );

    triangles[4].a = vertsCube[3]; triangles[4].aUV = vec2(0.0,0.0);
    triangles[4].b = vertsCube[7]; triangles[4].bUV = vec2(1.0,0.0);
    triangles[4].c = vertsCube[6];;triangles[4].cUV = vec2(1.0,1.0);
    triangles[4].n = vec3( 0.0, 1.0, 0.0 );
    triangles[5].a = vertsCube[3]; triangles[5].aUV = vec2(0.0,0.0);
    triangles[5].b = vertsCube[6]; triangles[5].bUV = vec2(1.0,1.0);
    triangles[5].c = vertsCube[2]; triangles[5].cUV = vec2(0.0,1.0);
    triangles[5].n = vec3( 0.0, 1.0, 0.0 );

    triangles[6].a = vertsCube[0]; triangles[6].aUV = vec2(1.0,0.0);
    triangles[6].b = vertsCube[6]; triangles[6].bUV = vec2(0.0,1.0);
    triangles[6].c = vertsCube[4]; triangles[6].cUV = vec2(0.0,0.0);
    triangles[6].n = vec3( 0.0, 0.0, -1.0 );
    triangles[7].a = vertsCube[0]; triangles[7].aUV = vec2(1.0,0.0);
    triangles[7].b = vertsCube[2]; triangles[7].bUV = vec2(1.0,1.0);
    triangles[7].c = vertsCube[6]; triangles[7].cUV = vec2(0.0,1.0);
    triangles[7].n = vec3( 0.0, 0.0, -1.0 );

    triangles[8].a = vertsCube[1]; triangles[8].aUV = vec2(1.0,0.0);
    triangles[8].b = vertsCube[2]; triangles[8].bUV = vec2(0.0,1.0);
    triangles[8].c = vertsCube[0]; triangles[8].cUV = vec2(0.0,0.0);
    triangles[8].n = vec3( -1.0, 0.0, 0.0 );
    triangles[9].a = vertsCube[1]; triangles[9].aUV = vec2(1.0,0.0);
    triangles[9].b = vertsCube[3]; triangles[9].bUV = vec2(1.0,1.0);
    triangles[9].c = vertsCube[2]; triangles[9].cUV = vec2(0.0,1.0);
    triangles[9].n = vec3( -1.0, 0.0, 0.0 );

    triangles[10].a = vertsCube[1]; triangles[10].aUV = vec2(0.0,0.0);
    triangles[10].b = vertsCube[0]; triangles[10].bUV = vec2(0.0,1.0);
    triangles[10].c = vertsCube[4]; triangles[10].cUV = vec2(1.0,1.0);
    triangles[10].n = vec3( 0.0, -1.0, 0.0 );
    triangles[11].a = vertsCube[1]; triangles[11].aUV = vec2(0.0,0.0);
    triangles[11].b = vertsCube[4]; triangles[11].bUV = vec2(1.0,1.0);
    triangles[11].c = vertsCube[5]; triangles[11].cUV = vec2(1.0,0.0);
    triangles[11].n = vec3( 0.0, -1.0, 0.0 );
}

float cross2d( in vec2 a, in vec2 b )
{
    return a.x*b.y - a.y*b.x;
}

vec3 pixelShader(in vec2 uv, in vec3 wnor )
{
		
	vec3 mate = texture( iChannel0, uv ).xyz * abs(wnor.x) + 
		        texture( iChannel1, uv ).xyz * abs(wnor.y) + 
		        texture( iChannel2, uv ).xyz * abs(wnor.z);
    return mate;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
	mat4 mdv = setTranslation( 0.0, 0.5, -5.0 ) * 
		       setRotation( 0.6, 0.0,  0.0 ) * 
		       setRotation( 0.0, 3.1*(0.3*iTime), 0.0 );

    vec2 px = (2.0*fragCoord-iResolution.xy) / iResolution.y;

    createCube();

    vec3 color = vec3( 0.0, 0.0, 0.0 );



    // render triangles
    for( int i=0; i<12; i++ )
    {
        // transform to eye space
        vec3 ep0 = (mdv * vec4(triangles[i].a,1.0)).xyz;
        vec3 ep1 = (mdv * vec4(triangles[i].b,1.0)).xyz;
        vec3 ep2 = (mdv * vec4(triangles[i].c,1.0)).xyz;
        vec3 nor = (mdv * vec4(triangles[i].n,0.0)).xyz;

        // transform to clip space
        float w0 = 1.0/ep0.z;
        float w1 = 1.0/ep1.z;
        float w2 = 1.0/ep2.z;

        vec2 cp0 = 2.0*ep0.xy * -w0;
        vec2 cp1 = 2.0*ep1.xy * -w1;
        vec2 cp2 = 2.0*ep2.xy * -w2;

        // fetch vertex attributes, and divide by z
        vec2 u0 = triangles[i].aUV * w0;
        vec2 u1 = triangles[i].bUV * w1;
        vec2 u2 = triangles[i].cUV * w2;

        //-----------------------------------
        // rasterize
        //-----------------------------------

        // calculate areas for subtriangles
        vec3 di = vec3( cross2d( cp1 - cp0, px - cp0 ), 
					    cross2d( cp2 - cp1, px - cp1 ), 
					    cross2d( cp0 - cp2, px - cp2 ) );
		
        // if all positive, point is inside triangle
        if( all(greaterThan(di,vec3(0.0))) )
        {
            // calc barycentric coordinates
            vec3 ba = di.yzx / (di.x+di.y+di.z);

            // barycentric interpolation of attributes and 1/z
            float iz = ba.x*w0 + ba.y*w1 + ba.z*w2;
            vec2  uv = ba.x*u0 + ba.y*u1 + ba.z*u2;

            // recover interpolated attributes
            float z = 1.0/iz;
            uv *= z;


			color = pixelShader(uv, triangles[i].n);
        }
    }

    fragColor = vec4(color,1.0);
}

效果:

这个作者的算法,看起来可以在任何充分可并行的结构中进行绘图,还是挺有意思的。这个解读仅仅业余时间消遣如果理解错误地方指出

更多推荐