WebGL三维模型实现Phong着色(WebGL进阶05)

2023-11-01 01:40

本文主要是介绍WebGL三维模型实现Phong着色(WebGL进阶05),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

WebGL三维模型实现Phong着色

  • 1. demo效果
  • 2. Phong着色与Gouraud 着色
    • 2.1Gouraud 着色
    • 2.2 Phong 着色
  • 3. 实现要点
  • 4. demo代码

1. demo效果

在这里插入图片描述
在这里插入图片描述

2. Phong着色与Gouraud 着色

通过前面几篇文章,大致知道典型的照明技术有三种:漫反射光(diffuse light)、环境光(ambient light)和反射光(specular light)。通过与它们组合使用可以实现更加逼真的照明效果。

2.1Gouraud 着色

前几篇文章的光照实现都是在顶点着色器实现的,会对每个三角形的顶点进行一次着色,这样的着色称为Gouraud 着色,Gouraud 着色时颜色信息从顶点着色器传向片元着色器的过程中会进行颜色插值,因此在颜色变化的边界处可能会出现锯齿。尤其是在顶点数比较少的时候。下图是Gouraud 着色的效果,可以看到红色框内的阴影有些许锯齿
在这里插入图片描述

2.2 Phong 着色

Gouraud着色是在顶点着色器处理处理完成在向片元着色器传输的过程中会进行颜色插值,Phong 着色是直接在片元着色器处理,是对每个像素进行颜色插值。与Gouraud着色相比,Phong 着色使用逐像素颜色插值来消除不自然的锯齿,计算量会大一些,但在相同顶点数量的情况下,可以获得更加平滑的阴影和更美丽自然的高光。下图是使用Phong着色实现的效果,可以与Gouraud着色的效果对比一下

Phong着色效果
在这里插入图片描述
Gouraud着色效果
在这里插入图片描述

3. 实现要点

与上一篇文章相比,这一次将计算光的过程放在了片元着色器中,但是在顶点着色器中需要定义varying变量vNormal和vColor向片元着色器中传值法线信息和顶点颜色信息

//顶点着色器
var VSHADER_SOURCE = `attribute vec3 position; //顶点位置信息attribute vec4 color; //颜色attribute vec3 normal; //法线uniform mat4 uMvpMatrix; //模型视图投影矩阵varying vec3 vNormal; //向片元着色器传值法线信息varying vec4 vColor; //向片元着色器传值颜色信息void main(){vNormal = normal;vColor = color;gl_Position = uMvpMatrix*vec4(position,1.0); //将模型视图投影矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position}`

在片元着色器中接收顶点着色器传来的法线信息和顶点颜色信息,然后分别计算慢反射光、反射光,并与接收到的环境颜色,使用公式:颜色 = 顶点颜色 * 漫反射光 + 反射光 + 环境光 计算着色使用的颜色

//片元着色器
var FSHADER_SOURCE = `#ifdef GL_ESprecision mediump float; // 设置float类型为中精度#endifuniform mat4 uInvMatrix;//模型坐标变换矩阵的逆矩阵uniform vec3 uLightDirection;//平行光方向uniform vec4 uAmbientLightColor;//环境光颜色uniform vec3 uEyeDirection;//视点方向varying vec3 vNormal; //接收法线信息varying vec4 vColor; //接收颜色信息void main(){vec3 invLight = normalize(uInvMatrix*vec4(uLightDirection,0.0)).xyz;vec3 invEye = normalize(uInvMatrix*vec4(uEyeDirection,0.0)).xyz;vec3 halfLE = normalize(invLight + invEye);//半程向量float diffuse = clamp(dot(vNormal,invLight),0.0,1.0);//将结果限定在0.0~1.0内float specular = pow(clamp(dot(vNormal, halfLE), 0.0, 1.0), 50.0);//计算高光//颜色 = 顶点颜色 * 漫反射光 + 反射光 + 环境光vec4 destColor = vColor*vec4(vec3(diffuse),1.0) + vec4(vec3(specular), 1.0)+uAmbientLightColor;gl_FragColor = destColor;//将计算的颜色信息赋值给内置变量gl_FragColor}
`

4. demo代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title></title>
</head><body><!--通过canvas标签创建一个800px*800px大小的画布--><canvas id="webgl" width="800" height="800"></canvas><script type="text/javascript" src="./lib/cuon-matrix.js"></script><script>//顶点着色器var VSHADER_SOURCE = `attribute vec3 position; //顶点位置信息attribute vec4 color; //颜色attribute vec3 normal; //法线uniform mat4 uMvpMatrix; //模型视图投影矩阵varying vec3 vNormal; //向片元着色器传值法线信息varying vec4 vColor; //向片元着色器传值颜色信息void main(){vNormal = normal;vColor = color;gl_Position = uMvpMatrix*vec4(position,1.0); //将模型视图投影矩阵与顶点坐标相乘赋值给顶点着色器内置变量gl_Position}`//片元着色器var FSHADER_SOURCE = `#ifdef GL_ESprecision mediump float; // 设置float类型为中精度#endifuniform mat4 uInvMatrix;//模型坐标变换矩阵的逆矩阵uniform vec3 uLightDirection;//平行光方向uniform vec4 uAmbientLightColor;//环境光颜色uniform vec3 uEyeDirection;//视点方向varying vec3 vNormal; //接收法线信息varying vec4 vColor; //接收颜色信息void main(){vec3 invLight = normalize(uInvMatrix*vec4(uLightDirection,0.0)).xyz;vec3 invEye = normalize(uInvMatrix*vec4(uEyeDirection,0.0)).xyz;vec3 halfLE = normalize(invLight + invEye);//半程向量float diffuse = clamp(dot(vNormal,invLight),0.0,1.0);//将结果限定在0.0~1.0内float specular = pow(clamp(dot(vNormal, halfLE), 0.0, 1.0), 50.0);//计算高光//颜色 = 顶点颜色 * 漫反射光 + 反射光 + 环境光vec4 destColor = vColor*vec4(vec3(diffuse),1.0) + vec4(vec3(specular), 1.0)+uAmbientLightColor;gl_FragColor = destColor;//将计算的颜色信息赋值给内置变量gl_FragColor}`onload = function () {//通过getElementById()方法获取canvas画布var canvas = document.getElementById('webgl');//通过方法getContext()获取WebGL上下文var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');//创建程序对象var prg = createProgram(VSHADER_SOURCE, FSHADER_SOURCE);//获取顶点位置、法线、颜色的存储地址var attLocations = {position: gl.getAttribLocation(prg, 'position'),normal: gl.getAttribLocation(prg, 'normal'),color: gl.getAttribLocation(prg, 'color'),}//每个顶点属性的大小(分量数)var attStrides = {position: 3,normal: 3,color: 4,}// 生成绘制甜圈圈的信息var torusData = torus(50, 50, 3.0, 8.0);var position = torusData[0];var normal = torusData[1];var color = torusData[2];var index = torusData[3];// 创建存放顶点、法线、颜色的VBOvar vbos = {position: create_vbo(position),normal: create_vbo(normal),color: create_vbo(color),}// 设置VBOset_attribute(vbos, attLocations, attStrides);// 创建IBOvar ibo = create_ibo(index);// IBO绑定gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);//获取uniform变量模型视图投影矩阵、模型坐标变换矩阵的逆矩阵、平行光方向、环境光颜色、视角方向var uniformLocations = {uMvpMatrix: gl.getUniformLocation(prg, 'uMvpMatrix'),uInvMatrix: gl.getUniformLocation(prg, 'uInvMatrix'),uLightDirection: gl.getUniformLocation(prg, 'uLightDirection'),uAmbientLightColor: gl.getUniformLocation(prg, 'uAmbientLightColor'),uEyeDirection: gl.getUniformLocation(prg, 'uEyeDirection'),}//给顶点着色器uniform变量uLightDirection-平行光方向传值[-2.5, 3.5, 0.5]gl.uniform3fv(uniformLocations.uLightDirection, [-2.5, 3.5, 0.5]);//给顶点着色器uniform变量uAmbientLightColor- 环境光颜色传值(0.2, 0.1, 0.2, 1.0)gl.uniform4f(uniformLocations.uAmbientLightColor, 0.2, 0.1, 0.2, 1.0);//给顶点着色器uniform变量uEyeDirection-视点方向传值[0.0, 0.0, 20.0]gl.uniform3fv(uniformLocations.uEyeDirection, [0.0, 0.0, 20.0]);var currentAngle = [0.0, 0.0]; //当前旋转的角度[x-axis, y-axis]var g_MvpMatrix = new Matrix4(); //模型视图投影矩阵 var viewProjMatrix = new Matrix4(); //创建视图投影矩阵var modelMatrix = new Matrix4(); //创建模型矩阵var invMatrix = new Matrix4(); //创建模型矩阵viewProjMatrix.setPerspective(45.0, canvas.width / canvas.height, 1.0, 100.0);viewProjMatrix.lookAt(0.0, 20.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);gl.enable(gl.DEPTH_TEST); //开启隐藏面消除gl.depthFunc(gl.LEQUAL); //如果传入值小于或等于深度缓冲区值,则通过gl.enable(gl.CULL_FACE); //激活多边形正反面剔除var rotateSpeed = 0.1; // 自动旋转速度(function tick() {// gl初始化gl.clearColor(0.0, 0.0, 0.0, 1.0); //指定调用 clear() 方法时使用的颜色值gl.clearDepth(1.0); //设置深度清除值gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); //清空颜色和深度缓冲区//计算模型视图投影矩阵 g_MvpMatrix.set(viewProjMatrix); //设置视图投影矩阵 modelMatrix.setRotate(currentAngle[0], 1.0, 0.0, 0.0); //沿X轴旋转设置矩阵modelMatrix.rotate(currentAngle[1], 0.0, 1.0, 0.0); //沿Y轴旋转设置矩阵rotateSpeed += 0.3;//modelMatrix.rotate(rotateSpeed, 0.0, 1.0, 1.0); //更新旋转矩阵g_MvpMatrix.multiply(modelMatrix) //相乘模型变换矩阵//计算模型坐标变换矩阵的逆矩阵invMatrix.setInverseOf(modelMatrix)//向着色器传值模型视图投影矩阵uMvpMatrix、模型坐标变换矩阵的逆矩阵uInvMatrixgl.uniformMatrix4fv(uniformLocations.uMvpMatrix, false, g_MvpMatrix.elements);gl.uniformMatrix4fv(uniformLocations.uInvMatrix, false, invMatrix.elements);//绘图gl.drawElements(gl.TRIANGLES, index.length, gl.UNSIGNED_SHORT, 0);gl.flush();requestAnimationFrame(tick)})();initEventHandlers(canvas, currentAngle) //注册鼠标事件//创建程序对象function createProgram(vshader, fshader) {//创建顶点着色器对象var vertexShader = loadShader(gl.VERTEX_SHADER, vshader);//创建片元着色器对象var fragmentShader = loadShader(gl.FRAGMENT_SHADER, fshader);if (!vertexShader || !fragmentShader) {return null}//创建程序对象programvar program = gl.createProgram();if (!gl.createProgram()) {return null}//分配顶点着色器和片元着色器到programgl.attachShader(program, vertexShader);gl.attachShader(program, fragmentShader);//链接programgl.linkProgram(program);//检查程序对象是否连接成功var linked = gl.getProgramParameter(program, gl.LINK_STATUS);if (!linked) {var error = gl.getProgramInfoLog(program);console.log('程序对象连接失败: ' + error);gl.deleteProgram(program);gl.deleteShader(fragmentShader);gl.deleteShader(vertexShader);return null}//使用programgl.useProgram(program);gl.program = program;//返回程序program对象return program}function loadShader(type, source) {// 创建顶点着色器对象var shader = gl.createShader(type);if (shader == null) {console.log('创建着色器失败');return null}// 引入着色器源代码gl.shaderSource(shader, source);// 编译着色器gl.compileShader(shader);// 检查顶是否编译成功var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);if (!compiled) {var error = gl.getShaderInfoLog(shader);console.log('编译着色器失败: ' + error);gl.deleteShader(shader);return null}return shader}function initEventHandlers(canvas, currentAngle) {var dragging = false; //默认鼠标拖动不旋转物体var lastX = -1,lastY = -1; //鼠标最后的位置canvas.onmousedown = function (ev) { //注册鼠标按下事件var x = ev.clientX,y = ev.clientY;//鼠标在物体上开始拖动var rect = ev.target.getBoundingClientRect();if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {lastX = x;lastY = y;dragging = true;}}//鼠标松开拖动结束canvas.onmouseup = function (ev) {dragging = false;}canvas.onmousemove = function (ev) { //注册鼠标移动事件var x = ev.clientX,y = ev.clientY;if (dragging) {var factor = 100 / canvas.height; //旋转因子var dx = factor * (x - lastX);var dy = factor * (y - lastY);//沿Y轴的旋转角度控制在-90到90度之间currentAngle[0] = Math.max(Math.min(currentAngle[0] + dy, 90.0), -90.0);currentAngle[1] = currentAngle[1] + dx;}lastX = x;lastY = y;}}// 创建VBOfunction create_vbo(data) {//创建缓冲区对象var vbo = gl.createBuffer();//绑定缓冲区到ARRAY_BUFFERgl.bindBuffer(gl.ARRAY_BUFFER, vbo);//将数据写入缓冲区对象gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);//解绑缓冲区gl.bindBuffer(gl.ARRAY_BUFFER, null);return vbo}// 向VBO写入数据function set_attribute(vbo, attLocation, attStride) {for (var key in vbo) {//绑定缓冲区到ARRAY_BUFFERgl.bindBuffer(gl.ARRAY_BUFFER, vbo[key]);//分配缓存区到指定地址gl.vertexAttribPointer(attLocation[key], attStride[key], gl.FLOAT, false, 0, 0);//开启缓冲区gl.enableVertexAttribArray(attLocation[key]);}}// 创建IBOfunction create_ibo(data) {//创建缓冲区对象var ibo = gl.createBuffer();//绑定缓冲区到ELEMENT_ARRAY_BUFFERgl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);//将数据写入缓冲区对象gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Int16Array(data), gl.STATIC_DRAW);//解绑缓冲区gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);return ibo;}//生成甜圈圈//第一个参数表示管道截面圆分段数,第二个参数表示管道圆的分段数,//第三个参数管道截面圆的半径。第四个参数表示从管道中心到管道截面圆中心的距离function torus(row, column, irad, orad) {var position = new Array(),normal = new Array(),color = new Array(),index = new Array();for (var i = 0; i <= row; i++) {var r = Math.PI * 2 / row * i; //管道圆上每个分段的弧度var rr = Math.cos(r);var ry = Math.sin(r);for (var ii = 0; ii <= column; ii++) {var tr = Math.PI * 2 / column * ii;//每个顶点位置的x、y、z分量var tx = (rr * irad + orad) * Math.cos(tr);var ty = ry * irad;var tz = (rr * irad + orad) * Math.sin(tr);var rx = rr * Math.cos(tr);var rz = rr * Math.sin(tr);position.push(tx, ty, tz);normal.push(rx, ry, rz);var tc = hsva(360 / column * ii, 1, 1, 1);color.push(tc[0], tc[1], tc[2], tc[3]);}}for (i = 0; i < row; i++) {for (ii = 0; ii < column; ii++) {r = (column + 1) * i + ii;index.push(r, r + column + 1, r + 1);index.push(r + column + 1, r + column + 2, r + 1);}}return [position, normal, color, index];}//将HSV颜色转换为RGB颜色function hsva(h, s, v, a) {if (s > 1 || v > 1 || a > 1) {return;}var th = h % 360;var i = Math.floor(th / 60);var f = th / 60 - i;var m = v * (1 - s);var n = v * (1 - s * f);var k = v * (1 - s * (1 - f));var color = new Array();if (!s > 0 && !s < 0) {color.push(v, v, v, a);} else {var r = new Array(v, n, m, m, k, v);var g = new Array(k, v, v, n, m, m);var b = new Array(m, m, k, v, v, n);color.push(r[i], g[i], b[i], a);}return color;}}</script>
</body></html>

这篇关于WebGL三维模型实现Phong着色(WebGL进阶05)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/319592

相关文章

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

RedHat运维-Linux文本操作基础-AWK进阶

你不用整理,跟着敲一遍,有个印象,然后把它保存到本地,以后要用再去看,如果有了新东西,你自个再添加。这是我参考牛客上的shell编程专项题,只不过换成了问答的方式而已。不用背,就算是我自己亲自敲,我现在好多也记不住。 1. 输出nowcoder.txt文件第5行的内容 2. 输出nowcoder.txt文件第6行的内容 3. 输出nowcoder.txt文件第7行的内容 4. 输出nowcode

【Linux进阶】UNIX体系结构分解——操作系统,内核,shell

1.什么是操作系统? 从严格意义上说,可将操作系统定义为一种软件,它控制计算机硬件资源,提供程序运行环境。我们通常将这种软件称为内核(kerel),因为它相对较小,而且位于环境的核心。  从广义上说,操作系统包括了内核和一些其他软件,这些软件使得计算机能够发挥作用,并使计算机具有自己的特生。这里所说的其他软件包括系统实用程序(system utility)、应用程序、shell以及公用函数库等

一份LLM资源清单围观技术大佬的日常;手把手教你在美国搭建「百万卡」AI数据中心;为啥大模型做不好简单的数学计算? | ShowMeAI日报

👀日报&周刊合集 | 🎡ShowMeAI官网 | 🧡 点赞关注评论拜托啦! 1. 为啥大模型做不好简单的数学计算?从大模型高考数学成绩不及格说起 司南评测体系 OpenCompass 选取 7 个大模型 (6 个开源模型+ GPT-4o),组织参与了 2024 年高考「新课标I卷」的语文、数学、英语考试,然后由经验丰富的判卷老师评判得分。 结果如上图所

大语言模型(LLMs)能够进行推理和规划吗?

大语言模型(LLMs),基本上是经过强化训练的 n-gram 模型,它们在网络规模的语言语料库(实际上,可以说是我们文明的知识库)上进行了训练,展现出了一种超乎预期的语言行为,引发了我们的广泛关注。从训练和操作的角度来看,LLMs 可以被认为是一种巨大的、非真实的记忆库,相当于为我们所有人提供了一个外部的系统 1(见图 1)。然而,它们表面上的多功能性让许多研究者好奇,这些模型是否也能在通常需要系

通过SSH隧道实现通过远程服务器上外网

搭建隧道 autossh -M 0 -f -D 1080 -C -N user1@remotehost##验证隧道是否生效,查看1080端口是否启动netstat -tuln | grep 1080## 测试ssh 隧道是否生效curl -x socks5h://127.0.0.1:1080 -I http://www.github.com 将autossh 设置为服务,隧道开机启动

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测

时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测 目录 时序预测 | MATLAB实现LSTM时间序列未来多步预测-递归预测基本介绍程序设计参考资料 基本介绍 MATLAB实现LSTM时间序列未来多步预测-递归预测。LSTM是一种含有LSTM区块(blocks)或其他的一种类神经网络,文献或其他资料中LSTM区块可能被描述成智能网络单元,因为

vue项目集成CanvasEditor实现Word在线编辑器

CanvasEditor实现Word在线编辑器 官网文档:https://hufe.club/canvas-editor-docs/guide/schema.html 源码地址:https://github.com/Hufe921/canvas-editor 前提声明: 由于CanvasEditor目前不支持vue、react 等框架开箱即用版,所以需要我们去Git下载源码,拿到其中两个主

android一键分享功能部分实现

为什么叫做部分实现呢,其实是我只实现一部分的分享。如新浪微博,那还有没去实现的是微信分享。还有一部分奇怪的问题:我QQ分享跟QQ空间的分享功能,我都没配置key那些都是原本集成就有的key也可以实现分享,谁清楚的麻烦详解下。 实现分享功能我们可以去www.mob.com这个网站集成。免费的,而且还有短信验证功能。等这分享研究完后就研究下短信验证功能。 开始实现步骤(新浪分享,以下是本人自己实现

基于Springboot + vue 的抗疫物质管理系统的设计与实现

目录 📚 前言 📑摘要 📑系统流程 📚 系统架构设计 📚 数据库设计 📚 系统功能的具体实现    💬 系统登录注册 系统登录 登录界面   用户添加  💬 抗疫列表展示模块     区域信息管理 添加物资详情 抗疫物资列表展示 抗疫物资申请 抗疫物资审核 ✒️ 源码实现 💖 源码获取 😁 联系方式 📚 前言 📑博客主页: