【WebGL连载教程八】H5开发3D引擎:着色器之变换颜色的三角形

版权声明:本文为博主原创文章,如需转载请注明出处,谢谢。喜欢请关注哟~ https://blog.csdn.net/sjt223857130/article/details/80137765

在上一节中,回顾一下,我们学习了如何利用WebGL来绘制一个三角形。

本节,我们来深入了解一下着色器相关的知识。

在前篇,我们已经接触过了 顶点着色器 (vertexShader)和 片断着色器(fragmentShader)。

片断着色器也被称做:像素着色器。我觉得这个更好!在以后的文章中将被统一这么叫,大家注意了。

本节要点:这次会升级上一篇的三角形,让它的颜色通过程序随着时间的流逝,不断的进行进行变化。

【1】首先认识一下我们的两个着色器:

(a)顶点着色器

(b)像素着色器

它们分工明确,显卡的执行速度很快,比CPU快多了。因为显卡的工作很单一,就干这一件事,所以效率超高。

一般几万次顶点处理对于显卡来说,轻轻松松,这就是它最擅长的工作。

【2】学习着色器代码程序的三个关键字变量:

一个着色器程序,和我们的C语言比较相似。它由一个main() 函数做为入口。

GLGL ES中有几种变量关键字定义方法,常见的有attribute,uniform,varying 这三个关键字。

(1)attribute 关键字:

                            通过方法getAttribLocation()获取顶点数据的索引位置, 然后利用方法vertexAttribPointer()

                            可以把类型数组创建的顶点数据传递给顶点着色器,然后逐顶点处理计算。

(2)uniform  关键字:

                            uniform关键字和attribute共同的作用是可以接收数据,不同点是接收的数据不同,

                            attribute关键字声明顶点数据,这些数据会逐顶点处理,uniform关键字声明的数据不是顶点数据,

                            这些数据往往都会重复利用,每个顶点对应的数据会有坐标、颜色、法向量等,

                            但是光线的颜色和方向对于所有的顶点都是通用的。

(3)varying  关键字:

                            主要作用是在顶点着色器向像素着色器中传递数据中。且顺序不可逆。

GLSL ES是强类型语言,不像JavaScript使用var关键字来声明所有变量。GLSL ES要求你具体地指明变量的数据类型。

【3】默认精度修饰符:precision precision-qualifier type;

 precision可以用来确定默认精度修饰符。type可以是int或float或采样器类型,precision-qualifier可以是lowp, mediump, 或者highp。任何其他类型和修饰符都会引起错误。如果type是float类型,那么该精度(precision-qualifier)将适用于所有无精度修饰符的浮点数声明(标量,向量,矩阵)。如果type是int类型,那么该精度(precision-qualifier)将适用于所有无精度修饰符的整型数声明(标量,向量)。包括全局变量声明,函数返回值声明,函数参数声明,和本地变量声明等。没有声明精度修饰符的变量将使用和它最近的precision语句中的精度。

在顶点着色器中有如下预定义的全局默认精度语句:

precision highp float;
precision highp int;
precision lowp sampler2D;
precision lowp samplerCube;


在像素着色器中有如下预定义的全局默认精度语句:

precision mediump int;
precision lowp sampler2D;
precision lowp samplerCube;


像素着色器没有默认的浮点数精度修饰符。因此,对于浮点数,浮点数向量和矩阵变量声明,

要么声明必须包含一个精度修饰符,要不默认的精度修饰符在之前已经被声明过了。

着色器还有许多知识将在以后逐步讲解,下面来开始改动程序,让三角形闪起来!

【4】更加高效的帧渲染循环(推荐!!):

我们想要三角形不停的变换颜色,首先需要一个不停执行的方法或循环。

在Web中推荐使用帧渲染模式的循环方法requestAnimationFrame,代码如下:

//帧循环
var animate: any = function (time: number) {

//要执行的程序。。。。。。

//启用帧循环
window.requestAnimationFrame(animate);
}
animate(0);

window.requestAnimationFrame() 将告知浏览器你马上要开始动画效果了,后者需要在下次动画前调用相应方法来更新画面。这个方法就是传递给window.requestAnimationFrame()的回调函数。

这个方法原理其实也就跟setTimeout/setInterval差不多,通过递归调用同一方法来不断更新画面以达到动起来的效果,但它优于setTimeout/setInterval的地方在于它是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销。

【5】更加准确的时间计时(推荐!!):

图形程序和游戏通常会跟踪一个时间差(Deltatime)变量,它储存了渲染上一帧所用的时间。我们把所有速度都去乘以deltaTime值。结果就是,如果我们的deltaTime很大,就意味着上一帧的渲染花费了更多时间,所以这一帧的速度需要变得更高来平衡渲染所花去的时间。使用这种方法时,无论你的电脑快还是慢,通过时间差计算的速度或颜色变换都会相应平衡,这样每个用户的体验就都一样了。不会发生有的电脑运行相同的代码,有的变化快,有的变化慢了。

var timeValue: number = 0.0;// 获取程序运行的秒数

var deltaTime: number = 0.0;// 当前帧与上一帧的时间差

var lastFrame: number = 0.0;// 上一帧的时间

//帧循环

var animate: any = function (time: number) {

//计算当前帧时间

var currentFrame: number = new Date().getTime();

deltaTime = currentFrame - lastFrame;

timeValue = timeValue + deltaTime / 1000;

lastFrame = currentFrame;

//要执行的程序。。。。。。



//启用帧循环

window.requestAnimationFrame(animate);

}

animate(0);

【6】实时渲染并更新像素着色器的颜色:

首先我们更改像素着色器的代码,增加一个uniform vec4 ourColor;用于接收颜色。

vec4 是一个包含4个值的数据float类型,可以用来表示  坐标值(x,y,z,w),向量值,颜色值(r,g,b,a)等等。

//片段着色器

var fragmentShaderSource: string = `

precision mediump float;

uniform vec4 ourColor;

void main()

{

gl_FragColor = ourColor;

}

`;

然后,在渲染前,给这个ourColor属性赋值。以下是赋值过程。


//不停的更新着色器颜色

var greenValue: number = Math.sin(timeValue) / 2.0 + 0.5;

//获取像素着色器中ourColor变量的位置

var vertexColorLocation: WebGLUniformLocation | null = gl.getUniformLocation(shaderProgram, "ourColor");

//给ourColor赋值变化的颜色

gl.uniform4f(vertexColorLocation, 0.0, greenValue, 0.0, 1.0);

运行你的代码,你会发现三角形的颜色,开始不停的闪烁渐变了。非常的酷,对吗?

以下是 HelloTriangles.ts 的全部内容:

namespace sunnyboxs {//namespace命名空间



export class HelloTriangles { //类名



constructor() //构造函数

{

//通过元素id来获取对象

var canvas: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById('my-canvas');

if (!canvas) {

console.log("错误:无法获取到 Canvas 元素!");

return;

}



//获取一个3d绘图上下文,表明我们将使用webgl相关的api

var gl: WebGLRenderingContext = this.create3DContext(canvas);

if (gl == null) {

console.log("错误:无法获取到 WebGL 上下文!");

return;

}



//设置WebGL渲染区域尺寸

gl.viewport(0, 0, canvas.clientWidth, canvas.clientHeight);



//顶点着色器

var vertexShaderSource: string = `

attribute vec4 a_Position;

void main()

{

gl_Position = vec4(a_Position.x, a_Position.y, a_Position.z, 1.0);

}

`;



//片段着色器

var fragmentShaderSource: string = `

precision mediump float;

uniform vec4 ourColor;

void main()

{

gl_FragColor = ourColor;

}

`;



// 生成并编译顶点着色器和片段着色器

// =========================================================================

// 首先是创建和编译顶点着色器

var vertexShader: WebGLShader | null = gl.createShader(gl.VERTEX_SHADER);

gl.shaderSource(vertexShader, vertexShaderSource);

gl.compileShader(vertexShader);//编译

// 检查着色器代码是否发生编译错误

if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {

var log: string | null = gl.getShaderInfoLog(vertexShader);

gl.deleteShader(vertexShader);

console.log("错误:编译vertex顶点着色器发生错误:" + log);

return;

}



// 片段着色器

var fragmentShader: WebGLShader | null = gl.createShader(gl.FRAGMENT_SHADER);

gl.shaderSource(fragmentShader, fragmentShaderSource);

gl.compileShader(fragmentShader);//编译

// 检查着色器代码是否发生编译错误

if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {

var log: string | null = gl.getShaderInfoLog(fragmentShader);

gl.deleteShader(fragmentShader);

console.log("错误:编译fragment片段着色器发生错误:" + log);

return;

}

// 链接到着色器

var shaderProgram: WebGLProgram | null = gl.createProgram();

gl.attachShader(shaderProgram, vertexShader);//导入顶点着色器

gl.attachShader(shaderProgram, fragmentShader);//导入片断着色器

gl.linkProgram(shaderProgram);//链接到着色器

// 检查链接时,是否发生错误

if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {

var log: string | null = gl.getProgramInfoLog(shaderProgram);

console.log("错误:链接到着色器时发生错误:" + log);

return;

}

//链接完成后可以释放源

gl.deleteShader(vertexShader);

gl.deleteShader(fragmentShader);




// 创建一个三角形的顶点数据

// =========================================================================

var vertices: number[] = [

-0.5, -0.5, 0.0, // left

0.5, -0.5, 0.0, // right

0.0, 0.5, 0.0 // top

];



// 创建并绑定一个VBO对象

var VBO: WebGLBuffer | null = gl.createBuffer();//创建一个VBO顶点Buffer

gl.bindBuffer(gl.ARRAY_BUFFER, VBO);//绑定

gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);//填充数据



//给指定的着色器变量a_Position赋值

var a_Position = gl.getAttribLocation(shaderProgram, 'a_Position');//获取地址

//参数:着色器中的位置,顶点大小,数据类型,是否标准化,步长,偏移量

gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);

gl.enableVertexAttribArray(a_Position);//激活




var timeValue: number = 0.0;// 获取程序运行的秒数

var deltaTime: number = 0.0;// 当前帧与上一帧的时间差

var lastFrame: number = 0.0;// 上一帧的时间

//帧循环

var animate: any = function (time: number) {

//计算当前帧时间

var currentFrame: number = new Date().getTime();

deltaTime = currentFrame - lastFrame;

timeValue = timeValue + deltaTime / 1000;

lastFrame = currentFrame;



//渲染代码开始

// =========================================================================

//清空指定<canvas>的颜色

gl.clearColor(0.2, 0.3, 0.3, 1.0);

//清空<canvas>

gl.clear(gl.COLOR_BUFFER_BIT);

// 使用着色器程序

gl.useProgram(shaderProgram);

// 绑定VBO

gl.bindBuffer(gl.ARRAY_BUFFER, VBO);



//不停的更新着色器颜色

var greenValue: number = Math.sin(timeValue) / 2.0 + 0.5;

//获取像素着色器中ourColor变量的位置

var vertexColorLocation: WebGLUniformLocation | null = gl.getUniformLocation(shaderProgram, "ourColor");

//给ourColor赋值变化的颜色

gl.uniform4f(vertexColorLocation, 0.0, greenValue, 0.0, 1.0);



// 画出三角形

gl.drawArrays(gl.TRIANGLES, 0, 3);

// =========================================================================

//渲染代码结束



//启用帧循环

window.requestAnimationFrame(animate);

}

animate(0);



}



//获取一个兼容的webgl上下文

private create3DContext(canvas: HTMLCanvasElement): WebGLRenderingContext | any {

var names: string[] = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];

for (var i = 0; i < names.length; i++) {

try {

var context: WebGLRenderingContext = <WebGLRenderingContext>canvas.getContext(names[i]);

if (context) {

return context;

}

} catch (e) { }

}

return null;

}

}

}

结尾,如果你不知道如何运行这段代码,请在本博客中查看前几篇项目搭建和编译教程!

下一节,我们继续一起来学习 WebGL !

欢迎关注或留言~

猜你喜欢

转载自blog.csdn.net/sjt223857130/article/details/80137765