当前位置: 首页 > article >正文

shader理解

文章目录

  • 1、什么是shader
  • 2、顶点着色器 Vertex Shader
  • 3、片元着色器fragment Shader
  • 4、shader是如何运行的
  • 5、如何在threejs中编写shader
  • 6、精度声明
  • 7、变量声明
  • 8、程序内容
  • 9、三维渲染机制
  • 10、举例
    • 1、渲染一个大气层
      • 代码解释
      • 最终渲染效果

1、什么是shader

所谓shader中文叫做着色器,它实际上是给用户一种方式来介入GPU渲染流程,定制gpu如何组织数据和绘制数据到屏幕上

2、顶点着色器 Vertex Shader

顶点着色器主要负责处理顶点数据,其实顶点着色器能做的事情并不多,大部分就是在处理顶点的矩阵变换,将顶点的位置通过MVP矩阵乘法最终变换到裁剪空间

输入:顶点着色器的输入数据一般是我们传入的attribute、uniforms变量。

输出:一般顶点着色器的运算结果输出是设置gl_Position,也可以设置一些变量比如gl_PointSize或者 varying变量

3、片元着色器fragment Shader

片元着色器在整个渲染中起到了非常大的作用,一般颜色,贴图采样,光照,阴影等计算都会在片元着色器中计算。

输入:片元着色器的输入数据一般是我们从顶点着色器传入的varying或者全局的uniforms变量。

输出:一般片元着色器的运算结果输出是设置gl_FragColor

4、shader是如何运行的

想要了解shader是如何运行的,我们就要先知道整个webgl的运行机制。webgl的一次绘制,需要经过大致的以下几个阶段。

  • 创建webgl的应用程序Program,从文本编译并使用shader
  • 将三维几何数据通过attribute传送给GPU
  • GPU执行顶点着色器,处理顶点数据
  • GPU执行片元着色器,处理颜色等数据
  • 将执行结果写入缓冲区(用于显示到屏幕或者后处理)
  • 我们可以看到,shader的执行是需要链接、编译后执行的,所以shader在运行时其实本身是不能修改的,但是我们可以修改一些数据参数值。

5、如何在threejs中编写shader

ok了解完了基本的一些基础概念后,我们来从一个例子里面,讲讲如何实际使用shader。由于threejs已经帮我们完成了很多基础的框架操作,我们只需要把精力专注在shader程序本身就好。

首先是shader存放的位置,我们可以将shader写成单独的文件,或者在js代码中使用字符串的形式,或者使用html页面中。我们本次演示的是html直接标签包含的形式

第一步,在html的页面中加入以上两个shader的script标签。

<script id="vertexShader" type="x-shader/x-vertex">precision mediump float;precision mediump int;uniform mat4 modelViewMatrix;uniform mat4 projectionMatrix;attribute vec3 position;varying vec3 vPosition;void main() {vPosition = position;gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );}</script>
<script id="fragmentShader" type="x-shader/x-fragment">precision mediump float;precision mediump int;uniform float ratio;varying vec3 vPosition;void main() {vec3 center = vec3( 0.0,0.0,0.0 );float dist=  distance(vPosition,center)/100.0; dist = clamp(dist,0.0,1.0); float color = 1.0-dist ; gl_FragColor =  vec4( color*ratio, color*ratio,0.0,dist );}</script>

shader本身的代码量非常少,只做简单的demo用。后面我们会讲解里面具体的内容。我们先继续整个流程

第二步,在我们的js代码中使用threejs的RawShaderMaterial来创建一个shader材质。并将html标签中的内容获取赋值给vertexShader,fragmentShader。同时,我们创建了一个uniform 名叫ratio。

const material = new THREE.RawShaderMaterial({ uniforms: {ratio: {value: 0.0}},vertexShader: document.getElementById('vertexShader').textContent,fragmentShader: document.getElementById('fragmentShader').textContent, 
});

最后一步,我们把这个材质放在一个平面上,并在主循环中更新uniform的值

const plane = new THREE.Mesh(geometry, material);
plane.rotateX(-Math.PI / 2); 
scene.add(plane);let next = 0;
const animate = function () {requestAnimationFrame(animate);next = next + 0.01;if (next > 1)next = 0;plane.material.uniforms.ratio.value = next;renderer.render(scene, camera);
};

6、精度声明

在webgl的shader中,我们可以在第一行使用precision关键字进行精度设置。声明变量精度高低的三个关键子lowp、mediump和highp。注意不同的shader里面有默认值,如果不指定或者指定错误,会导致编译报错

顶点着色器默认精度

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

片元着色器默认精度

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

7、变量声明

shader中的变量,一般分为三大类,分别是attribute、uniform、varying,他们具体不同的使用场景。

attribute 只能在顶点着色器中出现,且赋值的操作一般是由webgl组织数据的时候就已经绑定好了。attribute是用来向顶点着色器传输几何顶点数据的一般来说。

uniform 可以在顶点或者片元着色器中使用。但是uniform的值是只读的,不可以修改它的值,一般用来传递一些全局参数,比如mvp的矩阵等。

varying 的作用是将顶点着色器中的数据传递给片元着色器。这里的数据一般是一些顶点相关的属性,比如每个顶点的颜色。注意varying在传值的时候,会被gpu插值,所以到片元着色器的时候,值与原先的值不一定完全一致。

##glsl预定义的变量
预定义变量可以直接使用

  • gl_Position

    用于vertex shader, 写顶点位置;被图元收集、裁剪等固定操作功能所使用;其内部声明是:highp vec4 gl_Position

  • gl_FragColor

  • gl_FragData

    用于Fragment shader,是个数组,写gl_FragData[n] 为data n, gl_FragColor和gl_FragData是互斥的,不会同时写入

  • gl_FragCoord

    用于Fragment shader,只读, Fragment相对于窗口的坐标位置 x,y,z,1/w; 这个是固定管线图元差值后产生的;z 是深度值; mediump vec4 gl_FragCoord

  • gl_PointSize

    用于vertex shader, 写光栅化后的点大小,像素个数

  • gl_FrontFacing

    用于判断 fragment是否属于 front-facing primitive;只读

  • gl_PointCoord

    仅用于 point primitive; mediump vec2 gl_PointCoord

  • normalMatrix 法线矩阵

  • discard
    片段着色器中有一种特殊的控制流成为discard。使用discard会退出片段着色器,不执行后面的片段着色操作。片段也不会写入帧缓冲区。
    if (color.a < 0.9)
    discard;

8、程序内容

shader中有一个主函数,类似C语言。一般形式是

void main() {}

程序会从主入口开始执行,直到里面的所有代码全部执行完毕。

我们可以看到,示例中的顶点着色器主函数其实只有两句话,第一句是vPosition = position;表示将attribute的值赋值给varying用来传递给片元着色器。另外一句是gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );这句的作用是,通过矩阵运算,计算当前顶点在裁剪空间坐标点。

片元着色器中,其实也没有几句代码,我们大致讲解一下。我们首先定义了一个圆心vec3 center = vec3( 0.0,0.0,0.0 ); ;然后通过内置的distance函数计算当前顶点位置到圆心的距离float dist= distance(vPosition,center)/100.0;,并转化为(0-1)的区间dist = clamp(dist,0.0,1.0);;我们使用float color = 1.0-dist ;创建一个颜色值,约靠近圆心颜色越深。最后使用gl_FragColor = vec4( colorratio, colorratio,0.0,dist );给当前顶点着色输出。

通过代码分析,我们可以看到,其实shader的作用对象,都是非常微观的顶点,这是由于gpu并行运算的原因。所以,我们并不能知道顶点之间的关系。记住这点,我们在写shader的时候要时刻明白自己操作的对象,否则很容易陷入逻辑误区。

对于矩阵理解很费劲的话,强烈推荐看一看《3d数学基础》,里面介绍的很详细能学到很多知识,百度网盘链接,提取码:h1mi

9、三维渲染机制

设想在世界坐标系中,有一个任意方向任意位置的物体,我们要把它渲染到任意位置任意方向的摄像机中,为了做到这一点,必须将物体的所有顶点从物体坐标系中转换到世界坐标系中,接着再从世界坐标系中转换到摄像机坐标系中
P世界=P物体M物体→世界
P相机=P世界M世界→相机

10、举例

1、渲染一个大气层

var vertexShader	= [// 注意:varying定义的变量可以直接传递给片源着色器// 顶点着色器的变量传递给片元着色器//               世界坐标系模型顶点位置'varying vec3	vVertexWorldPosition;',//               单位法线向量'varying vec3	vVertexNormal;',//               颜色'varying vec4	vFragColor;','void main(){',		  //normalize 归一化函数 转为单位矢量//将法线向量转换到视图坐标系中'	vVertexNormal	= normalize(normalMatrix * normal);',//将顶点转换到世界坐标系中// position 世界坐标系下的位置'	vVertexWorldPosition	= (modelMatrix * vec4(position, 1.0)).xyz;','	// set gl_Position',// 通过矩阵运算,计算当前顶点在裁剪空间坐标点'	gl_Position	= projectionMatrix * modelViewMatrix * vec4(position, 1.0);','}'].join('\n');var fragmentShader2	= ['uniform vec3	glowColor;',//传递全局 只读    发光颜色'uniform float	coeficient;','uniform float	power;','varying vec3	vVertexNormal;','varying vec3	vVertexWorldPosition;','varying vec4	vFragColor;','void main(){',//世界坐标系中顶点位置到相机位置的向量'	vec3 worldVertexToCamera= cameraPosition - vVertexWorldPosition;',	//视图坐标系中从相机位置到顶点位置的向量'	vec3 viewCameraToVertex	= (viewMatrix * vec4(worldVertexToCamera, 0.0)).xyz;',//规一化 viewCameraToVertex视图坐标系中点到摄像机的距离向量'	viewCameraToVertex	= normalize(viewCameraToVertex);',//                                     视图坐标系下法线向量        视图坐标系下相机到顶点的单位向量'	float intensity		= coeficient + dot(vVertexNormal, viewCameraToVertex);',//'	if(intensity > 0.55){ intensity = 0.0;}','	gl_FragColor		= vec4(glowColor, intensity);','}'].join('\n');//本质透明度递减var sphere =  new THREE.SphereBufferGeometry( 12, 32, 32 );material_glow	= new THREE.ShaderMaterial({uniforms: {coeficient	: {type	: "f",value	: 0.0},power		: {type	: "f",value	: 2},glowColor	: {type	: "c",value	: new THREE.Color('blue')}},vertexShader	: vertexShader,fragmentShader	: fragmentShader2,blending	: THREE.NormalBlending,transparent	: true});mesh = new THREE.Mesh(sphere, material_glow);

代码解释

总体过程: 我们在世界坐标系中创建了一个球、添加了相机,然后将其转换为统一的相机坐标系(视图坐标系),判断法向量与相机至顶点向量间的夹角大小(即向量点积反应向量的方向的相似程度),来确定片源着色器渲染像素的透明度

先理解几个概念

  • 相机坐标系
    渲染一般是在相机坐标下判断位置关系,模拟眼睛看的世界,最终转换为二位平面渲染到屏幕上,示意图如下
    在这里插入图片描述
  • 法向量
    如下图绿色即为法向量,三维模型是用一个一个的点构成的,在前面的各种空间变换中,对顶点的位置信息进行了各种计算,但是要想构成一个模型光知道顶点的位置信息是不够的,有一个问题就是,如果我们用几个点构成了一个平面之后,我们该怎么确定哪一面是平面的正面?
    在这里插入图片描述

法线就是为了确定模型的正面是朝向哪里而存在的。法线(normal)是定义一个点的朝向的信息,它是一个矢量信息,所以也被称为法矢量(normal vector)。当我们变换一个模型的时候,也必须要变换模型的顶点法线,用来在后续的渲染步骤中计算光照,法线可以说是shader中最重要的信息之一,法线出错会导致各种奇怪的显示问题。而如果利用好法线,则可以在shader中实现各种炫酷的效果。

法线有一个特殊的空间为切线空间,切线矢量(tangent vector)也是顶点带有的一个信息。构成一个三维坐标空间的要素是原点和三个互相垂直的矢量。每个顶点有其自身的切线空间,也就是说每个顶点都是自身切线空间的原点,而法线这个矢量就是它的切线空间的Z轴。它的X轴和Y轴是怎么定义的?

在理解切线空间之前必须先理解一下纹理空间,如果想要把一张图片贴到3D模型上面,就必须要把模型的每个位置对应到图片上,等于是把一个物体的皮扒下来展平在图片上。这个图片所在的空间就是纹理空间,纹理空间是一个二维空间,每一个三维模型的顶点都在纹理空间有一个确定的坐标。

在这里插入图片描述
这也是为什么物体顶点向量转为相机坐标系下是这样(viewMatrix * vec4(worldVertexToCamera, 0.0)).xyz,然而法向量转为相机坐标系下是这样normalMatrix * normal

视图坐标系下法线向量 与相机到顶点的单位向量夹角如下

在这里插入图片描述

最终渲染效果

在这里插入图片描述

GLSL语言基础

http://www.lryc.cn/news/2412780.html

相关文章:

  • 全网最全的权限系统设计方案(图解)
  • 什么是API?(看完你就理解了)
  • stpcpy和stpncpy用法 strcpy和strncpy用法
  • strcat,strncat函数详解
  • Tomcat之点击startup.bat出现闪退的解决办法
  • opencv Scalar()的使用 心得
  • 【目标检测】39、一文看懂计算机视觉中的数据增强
  • NoSQL数据库Redis使用命令简介
  • textarea控件
  • 如何打开DMG文件
  • VMware和Ubuntu安装教程(2024年最新超详细!每个步骤都有)
  • 一文完全弄懂EndPoint组件
  • Linux之wc命令详解
  • Glide介绍及基本使用方法
  • trunc和date_trunc的区别
  • linux安装openoffice
  • PWA 入门教程
  • 容器化应用的救命稻草:K3s 备份和恢复中文指南
  • wampserver 64位是一款windows系统下的Apache+PHP+Mysql集成环境整合包
  • Code::Blocks 安装及使用
  • 【数据库】SQL基础知识总结
  • 正则表达式详解及Java代码示例。
  • Cookie是什么?怎么使用
  • 一文看懂LEC在IC设计中的重要性
  • 高颜值 UI!!国产 Redis 可视化工具,牛逼!
  • 从CentOS官网下载系统镜像详细教程
  • [四] WPF灵魂-Binding
  • FAT32文件系统格式详解
  • 百度云:人脸识别API接入
  • SQL Server 基础系列篇