5-颜色与纹理

一、学习要点

  1. 将顶点的其他非坐标数据 如颜色 等传入顶点的着色器
  2. 发生在顶点着色器和片元着色器之间的从图形到片元的转化,又称为图元光栅化(rasterzation process)
  3. 将图形或纹理映射到图形或者三维对象的表面上。

二、将非坐标数据传入顶点着色器

回顾一下顶点坐标传入着色器的步骤

  1. 创建缓冲区对象。
  2. 将缓冲区对象绑定到target上。
  3. 将顶点坐标数据写入缓冲区对象。
  4. 将缓冲区对象分配给对应的attribute对象。
  5. 开启attribute变量。
// 顶点着色器
var VSHANER_SOURCE =
    `
     attribute vec4 a_Position;
     attribute float a_PointSize;
     void main() {
        gl_Position = a_Position; //设置坐标
        gl_PointSize = a_PointSize; // 设置尺寸
     }`;
// 片元着色器程序
var FSHADER_SOURCE =
    `void main() {
        gl_FragColor = vec4(1.0,0.0,0.0,1.0); // 设置颜色
    }`;

function main() {
    let canvas = document.getElementById('webgl');
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log('error');
        return;
    }
    // 初始化着色器
    if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
        console.log('error');
        return;
    }
    //******* 设置顶点的位置********
    var n=initVertexBuffers(gl);
    if(n<0){
        console.log("error");
        return;
    }

    gl.clearColor(0.0,0.0,0.0,1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.POINTS,0,n);
}

function initVertexBuffers(gl) {
    var vertices = new Float32Array([
        0.0,0.5,-0.5,-0.5,0.5,-0.5
    ]);
    var n =3 ; // 点的个数
    // 创建缓冲区对象
    var vertexBuffer = gl.createBuffer();
    var sizeBuffer = gl.createBuffer();
    if(! vertexBuffer){
        console.log("error");
        return -1;
    }
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,vertices,gl.STATIC_DRAW);
    var a_Position = gl.getAttribLocation(gl.program,'a_Position');
    // 将缓冲区对象分配给 a_Position变量
    gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,0,0);
    gl.enableVertexAttribArray(a_Position);

    var sizes = new Float32Array([
        10.0,20.0,30.0
    ])
    // 将顶点尺寸写入缓冲区对象并开启
    gl.bindBuffer(gl.ARRAY_BUFFER,sizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,sizes,gl.STATIC_DRAW);
    var a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
    gl.vertexAttribPointer(a_PointSize,1,gl.FLOAT,false,0,0);
    gl.enableVertexAttribArray(a_PointSize);

    return n;
}

可见通过为顶点的每种数据建立一个缓冲区,然后分配给队形的attribute变量,你就可以向顶点着色器传递多分逐顶点的数据信息了。

这种写法适用于数据量不大的情况,当程序中复杂的三维图形具有成千上万个顶点是,维护所有的顶点数据是很困难的。可以将顶点的坐标和尺寸数据交错组织。使用 gl.vertexAttribPointer()的第五各参数 stride 和第六个参数 offset (我们称之为步进和偏移参数)

// 需要改动这个方法
function initVertexBuffers(gl) {
    var verticesSize = new Float32Array([
        0.0,0.5,10.0,
        -0.5,-0.5,20.0,
        0.5,-0.5,30.0
    ]);
    var n =3 ; // 点的个数
    // 创建缓冲区对象
    var vertexsizeBuffer = gl.createBuffer();
    if(! vertexsizeBuffer){
        console.log("error");
        return -1;
    }
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexsizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,verticesSize,gl.STATIC_DRAW);
    // 返回数组中每个元素占内存的大小
    var FSIZE = verticesSize.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program,'a_Position');
    // 将缓冲区对象分配给 a_Position变量
    gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,FSIZE * 3,0);
    gl.enableVertexAttribArray(a_Position);
    var a_PointSize = gl.getAttribLocation(gl.program,'a_PointSize');
    gl.vertexAttribPointer(a_PointSize,1,gl.FLOAT,false,FSIZE * 3,FSIZE * 2);
    gl.enableVertexAttribArray(a_PointSize);
    return n;
}

三、修改颜色(varying变量)

通过给顶点着色去的变量赋颜色值,然后顶点着色器和片元着色器交流,才能使传入顶点着色器的数据进入片元着色器,进而修改颜色。使用新的变量varying 变量 想片元着色器传入数据。 这里有一个注意的坑 一定要在片元着色器中用 precision mediump float; 声明浮点数精度。要不然会报错 ERROR: 0:2: '' : No precision specified for (float)

顶点着色器向片元着色器传值 只要使用varying 变量就可以 变量名一样就行。

// 顶点着色器
var VSHANER_SOURCE =
    `
     attribute vec4 a_Position;
     attribute vec4 a_Color;
     varying vec4 v_Color;
     void main() {
        gl_Position = a_Position; //设置坐标
        gl_PointSize = 10.0; // 设置尺寸
        v_Color = a_Color;  // 将数据传给片元着色器
     }`;
// 片元着色器程序
var FSHADER_SOURCE =
    `
    precision mediump float; // 需要声明浮点数精度否则报错
    varying vec4 v_Color;
    void main() {
        gl_FragColor = v_Color; // 设置颜色
    }`;

function main() {
    let canvas = document.getElementById('webgl');
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log('error');
        return;
    }
    // 初始化着色器
    if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
        console.log('error');
        return;
    }
    //******* 设置顶点的位置********
    var n=initVertexBuffers(gl);
    if(n<0){
        console.log("error");
        return;
    }

    gl.clearColor(0.0,0.0,0.0,1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.POINTS,0,n);
}

function initVertexBuffers(gl) {
    var verticesSize = new Float32Array([
        0.0,0.5,1.0,0.0,0.0,
        -0.5,-0.5,0.0,1.0,0.0,
        0.5,-0.5,0.0,0.0,1.0
    ]);
    var n =3 ; // 点的个数
    // 创建缓冲区对象
    var vertexsizeBuffer = gl.createBuffer();
    if(! vertexsizeBuffer){
        console.log("error");
        return -1;
    }
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexsizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,verticesSize,gl.STATIC_DRAW);
    var FSIZE = verticesSize.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program,'a_Position');
    // 将缓冲区对象分配给 a_Position变量
    gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,FSIZE * 5,0);
    gl.enableVertexAttribArray(a_Position);
    var a_Color = gl.getAttribLocation(gl.program,'a_Color');
    gl.vertexAttribPointer(a_Color,3,gl.FLOAT,false,FSIZE * 5,FSIZE * 2);
    gl.enableVertexAttribArray(a_Color);
    return n;
}

如果将 gl.drawArrays(gl.POINTS,0,n); 改成 gl.drawArrays(gl.TRIANGLES,0,n); 就会变成一个平滑过渡的三角形。

四、彩色三角形

片元着色器是如何工作的? 顶点卓泽强和片元着色器之间有两个步骤。

  1. 图形装配过程: 这一步的任务是,将鼓励的订单坐标装配成几何图形。几何图形的类别由gl.drawArrays()函数第一个参数决定
  2. 光栅化过程: 这一步的任务是,将配置好的几何图形转化成片元。过程如下图:

微信图片_20190422175044.png 3. 调用片元着色器

 // 为每个像素赋值不同的颜色
// 顶点着色器
var VSHANER_SOURCE =
    `
     attribute vec4 a_Position;
     attribute vec4 a_Color;
     void main() {
        gl_Position = a_Position; //设置坐标
        gl_PointSize = 10.0; // 设置尺寸
     }`;
// 片元着色器程序
var FSHADER_SOURCE =
    `
    precision mediump float; // 需要声明浮点数精度否则报错
    uniform float u_Width;
    uniform float u_Height; 
    // gl_Fragcoord是一个内置变量第一个和第二个分量表示 片元 在canvas坐标系统中的坐标
    void main() {
        gl_FragColor = vec4(gl_FragCoord.x/u_Width,0.0,gl_FragCoord.y/u_Height,1.0);
    }`;

function main() {
    let canvas = document.getElementById('webgl');
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log('error');
        return;
    }
    // 初始化着色器
    if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
        console.log('error');
        return;
    }
    //******* 设置顶点的位置********
    var n=initVertexBuffers(gl);
    if(n<0){
        console.log("error");
        return;
    }

    gl.clearColor(0.0,0.0,0.0,1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLES,0,n);
}

function initVertexBuffers(gl) {
    var verticesSize = new Float32Array([
        0.0,0.5,1.0,0.0,0.0,
        -0.5,-0.5,0.0,1.0,0.0,
        0.5,-0.5,0.0,0.0,1.0
    ]);
    var n =3 ; // 点的个数
    // 创建缓冲区对象
    var vertexsizeBuffer = gl.createBuffer();
    if(! vertexsizeBuffer){
        console.log("error");
        return -1;
    }
    // 将缓冲区对象绑定到目标
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexsizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,verticesSize,gl.STATIC_DRAW);
    var FSIZE = verticesSize.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program,'a_Position');
    // 将缓冲区对象分配给 a_Position变量
    gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,FSIZE * 5,0);
    gl.enableVertexAttribArray(a_Position);
    // 给 uniform 变量赋值
    var w = gl.drawingBufferWidth; // 获取颜色缓冲区的宽度
    var h = gl.drawingBufferHeight; // 获取颜色缓冲区的高度
    console.log(w);
    console.log(h);
    var u_Width = gl.getUniformLocation(gl.program,'u_Width');
    var u_Height = gl.getUniformLocation(gl.program,'u_Height');
    gl.uniform1f(u_Width,w);
    gl.uniform1f(u_Height,h);
    return n;
}

五 varying变量的内插过程

准确的说 顶点着色器中的v_color 变量和片元着色器中的v_color变量实际上不是一回事。他在传值的过程中经过了一个内插的过程。

微信图片_20190423094521.png

六、在矩形表面贴上图像

在三维图形学中,有一个很重要的技术叫做纹理映射。将一张真是世界的图片铁道一个由两个三角形组成的矩形上这样的矩形表面看起来就是这张图片。此时这张图片游客成文纹理图(texture image)或 纹理(texture),其本质就是未致歉光栅化后的每个片元图上合适的颜色。主要有一下四步:

  1. 准备好映射到集合图形上的纹理图像
  2. 为集合图形纹理配置纹理映射方式
  3. 加载纹理图像,对其进行一些配置,以在webgl中使用它。
  4. 在片元着色器中奖相应的文素从纹理中抽取出来,并将文素(组成纹理图像的像素)的颜色赋给片元。

纹理坐标:用来确定纹理图像的哪部分将覆盖到集合图形上。通过纹理坐标可以在纹理图像上获取文素颜色。Webgl使用s 和 t 命名纹理坐标(st坐标系统)。 微信图片_20190423095553.png

将纹理图形黏贴到集合图形上。 微信图片_20190423095808.png

// 顶点着色器
var VSHANER_SOURCE =
    `
     attribute vec4 a_Position;
     attribute vec2 a_TexCoord;
     varying vec2 v_TexCoord;
     void main() {
        gl_Position = a_Position; //设置坐标
        v_TexCoord = a_TexCoord;
     }`;
// 片元着色器程序
var FSHADER_SOURCE =
    `
    precision mediump float; // 需要声明浮点数精度否则报错
    uniform sampler2D u_Sampler;
    varying vec2 v_TexCoord;
    void main() {
        gl_FragColor = texture2D(u_Sampler,v_TexCoord);
    }`;

function main() {
    let canvas = document.getElementById('webgl');
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log('error');
        return;
    }
    // 初始化着色器
    if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
        console.log('error');
        return;
    }
    //******* 设置顶点的位置********
    var n=initVertexBuffers(gl);
    if(n<0){
        console.log("error");
        return;
    }
    // 配置纹理
    if(!initTextures(gl,n)){
        console.log("error");
        return;
    }

}

function initVertexBuffers(gl) {
    var verticeTexCoords = new Float32Array([
        // 顶点坐标,纹理坐标
        -0.5,0.5,-0.3,1.7,
        -0.5,-0.5,-0.3,-0.2,
        0.5,0.5,1.7,1.7,
        0.5,-0.5,1.7,-0.2
    ]);
    var n =4 ; // 点的个数
    // 创建缓冲区对象
    var vertexsizeBuffer = gl.createBuffer();
    if(! vertexsizeBuffer){
        console.log("error");
        return -1;
    }
    // 将顶点坐标和纹理坐标写入缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexsizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,verticeTexCoords,gl.STATIC_DRAW);
    var FSIZE = verticeTexCoords.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program,'a_Position');
    // 将缓冲区对象分配给 a_Position变量
    gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,FSIZE * 4,0);
    gl.enableVertexAttribArray(a_Position);
    // 将纹理坐标分配给a_TexCoord并启用它
    var a_TexCoord = gl.getAttribLocation(gl.program,'a_TexCoord');
    gl.vertexAttribPointer(a_TexCoord,2,gl.FLOAT,false,FSIZE*4,FSIZE*2);
    gl.enableVertexAttribArray(a_TexCoord);

    return n;
}


function initTextures(gl,n) {
    var texture = gl.createTexture(); // 创建纹理对象
    // 获取u_Sampler 的存储位置
    var u_Sampler = gl.getUniformLocation(gl.program,'u_Sampler');
    var image = new Image();//创建一个image对象
    // image图像加载事件的响应函数
    image.onload = function () {
        loadTexture(gl,n,texture,u_Sampler,image);
    }
    image.src = './../sky.jpg'
    return true
}


function loadTexture(gl,n,texture,u_Sampler,image) {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);// 对纹理进行Y轴翻转
    // 开始0号纹理单元
    gl.activeTexture(gl.TEXTURE0);
    // 向target绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D,texture);
    // 配置纹理参数 这四个配置主要是因为图像不一定都是2的幂次方 所以需要放大缩小
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texImage2D(gl.TEXTURE_2D,0,gl.RGB,gl.RGB,gl.UNSIGNED_BYTE,image);
    // 将0号纹理传递给着色器
    gl.uniform1i(u_Sampler,0);
    gl.clearColor(0.0,0.0,0.0,1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLE_STRIP,0,n);// 绘制矩形
}

七 使用多幅纹理

最关键的是,你需要对每一幅纹理分布进行前一节所描述的奖纹理图像映射到图形表面的操作,以此来讲多张纹理图像同时贴到图形上去。

// 顶点着色器
var VSHANER_SOURCE =
    `
     attribute vec4 a_Position;
     attribute vec2 a_TexCoord;
     varying vec2 v_TexCoord;
     void main() {
        gl_Position = a_Position; //设置坐标
        v_TexCoord = a_TexCoord;
     }`;
// 片元着色器程序
var FSHADER_SOURCE =
    `
    precision mediump float; // 需要声明浮点数精度否则报错
    uniform sampler2D u_Sampler0;
    uniform sampler2D u_Sampler1;
    varying vec2 v_TexCoord;
    void main() {
        vec4 color0 = texture2D(u_Sampler0,v_TexCoord);
        vec4 color1 = texture2D(u_Sampler1,v_TexCoord);
        gl_FragColor = color0 * color1;
    }`;

function main() {
    let canvas = document.getElementById('webgl');
    var gl = getWebGLContext(canvas);
    if(!gl){
        console.log('error');
        return;
    }
    // 初始化着色器
    if(!initShaders(gl,VSHANER_SOURCE,FSHADER_SOURCE)){
        console.log('error');
        return;
    }
    //******* 设置顶点的位置********
    var n=initVertexBuffers(gl);
    if(n<0){
        console.log("error");
        return;
    }

    // 配置纹理
    if(!initTextures(gl,n)){
        console.log("error");
        return;
    }

}

function initVertexBuffers(gl) {
    var verticeTexCoords = new Float32Array([
        // 顶点坐标,纹理坐标
        -0.5,0.5,0.0,1.0,
        -0.5,-0.5,0.0,0.0,
        0.5,0.5,1.0,1.0,
        0.5,-0.5,1.0,0.0
    ]);
    var n =4 ; // 点的个数
    // 创建缓冲区对象
    var vertexsizeBuffer = gl.createBuffer();
    if(! vertexsizeBuffer){
        console.log("error");
        return -1;
    }
    // 将顶点坐标和纹理坐标写入缓冲区对象
    gl.bindBuffer(gl.ARRAY_BUFFER,vertexsizeBuffer);
    gl.bufferData(gl.ARRAY_BUFFER,verticeTexCoords,gl.STATIC_DRAW);
    var FSIZE = verticeTexCoords.BYTES_PER_ELEMENT;
    var a_Position = gl.getAttribLocation(gl.program,'a_Position');
    // 将缓冲区对象分配给 a_Position变量
    gl.vertexAttribPointer(a_Position,2,gl.FLOAT,false,FSIZE * 4,0);
    gl.enableVertexAttribArray(a_Position);
    // 将纹理坐标分配给a_TexCoord并启用它
    var a_TexCoord = gl.getAttribLocation(gl.program,'a_TexCoord');
    gl.vertexAttribPointer(a_TexCoord,2,gl.FLOAT,false,FSIZE*4,FSIZE*2);
    gl.enableVertexAttribArray(a_TexCoord);
    return n;
}


function initTextures(gl,n) {
    var texture0 = gl.createTexture(); // 创建纹理对象
    var texture1 = gl.createTexture(); // 创建纹理对象
    // 获取u_Sampler 的存储位置
    var u_Sampler0 = gl.getUniformLocation(gl.program,'u_Sampler0');
    var u_Sampler1 = gl.getUniformLocation(gl.program,'u_Sampler1');
    var image0 = new Image();//创建一个image对象
    var image1 = new Image();//创建一个image对象
    // image图像加载事件的响应函数
    image0.onload = function () {
        loadTexture(gl,n,texture0,u_Sampler0,image0,0);
    }
    image1.onload = function () {
        loadTexture(gl,n,texture1,u_Sampler1,image1,1);
    }
    image0.src = './../sky.jpg';
    image1.src = './../timg.png';
    return true
}

// 标记纹理单元是否已经就绪
var g_texUnit0 = false , g_texUnit1 = false;

function loadTexture(gl,n,texture,u_Sampler,image,texUnit) {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL,1);// 对纹理进行Y轴翻转
    // 激活纹理单元
    if(texUnit == 0){
        gl.activeTexture(gl.TEXTURE0);
        g_texUnit0 = true;
    }else{
        gl.activeTexture(gl.TEXTURE1);
        g_texUnit1 = true;
    }
    // 向target绑定纹理对象
    gl.bindTexture(gl.TEXTURE_2D,texture);
    // 配置纹理参数
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    // 设置纹理图像
    gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,image);
    gl.clearColor(0.0,0.0,0.0,1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    // 将纹理传递给着色器
    gl.uniform1i(u_Sampler,texUnit);
    if( g_texUnit0 && g_texUnit1){

        gl.drawArrays(gl.TRIANGLE_STRIP,0,n);// 绘制矩形
    }
}

重点讲一下坐标的对应关系 这块坑了我很久

   var verticeTexCoords = new Float32Array([
        // 顶点坐标,纹理坐标
        -0.5,0.5,0.0,1.0,
        -0.5,-0.5,0.0,0.0,
        0.5,0.5,1.0,1.0,
        0.5,-0.5,1.0,0.0
    ]);

微信图片_20190423095808.png

   var verticeTexCoords = new Float32Array([
        // 顶点坐标,纹理坐标
        -0.5,0.5,-0.3,1.7,
        -0.5,-0.5,-0.3,-0.2,
        0.5,0.5,1.7,1.7,
        0.5,-0.5,1.7,-0.2
    ]);

微信图片_20190423095808.png

迄今为止我们已经学习了二维绘图的全部寄出技能。下面进入三维的世界。下一章 系统的熟悉一下OpenGL ES着色器语言