最近想使用threejs实现辉光的特效,在查阅了一些资料之后,不仅实现了辉光的特效,还顺带实现了大气层特效,在写着色器的过程中有了一些理解~
首要目标:为了实现辉光的着色器,我们把辉光分为几个步骤来实现:
1.创建两个球体,一个作为原始物体,一个略大一些作为它的辉光。
2.作为辉光的球体从内到外片元透明度逐渐减小(线性减小或是指数减小都可以)
3.将覆盖原始物体的部分丢弃掉
有了这3点步骤,做起来就会容易很多,这里我着重介绍一下着色器的部分:
首先是片元着色器
'varying vec3 vVertexWorldPosition;',
'varying vec3 vVertexNormal;',
'varying vec4 vFragColor;',
'void main(){',
' vVertexNormal = normalize(normalMatrix * normal);',//将法线转换到视图坐标系中
' vVertexWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;',//将顶点转换到世界坐标系中
' // set gl_Position',
' gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
'}'
这里做的事情比较简单,主要就是把法线转换到视图坐标系中和把顶点转换到世界坐标系中,因为在片元着色器中需要用到这两个参数,必须对他们进行一下转换,这里还要说一下的就是OpenGL用的是左乘,也就是顶点左乘矩阵,进行转换(对坐标系转换不熟悉的同学可以看一下我之前总结的文章:http://blog.csdn.net/srk19960903/article/details/77970630)。
然后是辉光的片元着色器:
'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 = normalize(viewCameraToVertex);',//规一化
' float intensity = coeficient + dot(vVertexNormal, viewCameraToVertex);',
' if(intensity > 0.55){ intensity = 0.0;}',
' gl_FragColor = vec4(glowColor, intensity);',
这里首先得到了两个向量,一是顶点到相机的向量然后将其转换到视图坐标系中,在进行归一化。二是直接传过来的视图坐标系中的法向量,这时将两个向量进行点乘就可以获得它们夹角的cos值,从圆心到圆边这个角度依次增大,所以乘积依次减小,最后把这个值加上一个常量赋给最终片元的不透明度,从而实现辉光的效果。
这里的难点主要在于两个:1.将顶点到相机的距离和法向量,统统转换到view坐标系中来计算。
2.两个向量的乘积所代表的意义,所夹角度的cos值,通过观察cos的函数图像我们可以看出他的变换规律,再根据规律写着色器。
大气层着色器
既然我们会写从内向外透明度逐渐降低的着色器,那么从外向内透明度逐渐降低的呢~
还记得在算点积的时候我们用的是那两个向量吗?顶点到摄像机&顶点向外的法向量,嗯,那么我们可以用从摄像机到顶点&顶点向外的法向量来重新算一次!这次由于角度开始就是大于180度的,从球心到球边,cos值从负值到正值进行变换,我们再用一个指数函数的形式来影响它的变换过程,就是这么简单,大气层的着色器就研究出来啦!
'uniform vec3 glowColor;',
'uniform float coeficient;',
'uniform float power;',
'varying vec3 vVertexNormal;',
'varying vec3 vVertexWorldPosition;',
'varying vec4 vFragColor;',
'void main(){',
' vec3 worldCameraToVertex= vVertexWorldPosition - cameraPosition;', //世界坐标系中从相机位置到顶点位置的距离
' vec3 viewCameraToVertex = (viewMatrix * vec4(worldCameraToVertex, 0.0)).xyz;',//视图坐标系中从相机位置到顶点位置的距离
' viewCameraToVertex = normalize(viewCameraToVertex);',//规一化
' float intensity = pow(coeficient + dot(vVertexNormal, viewCameraToVertex), power);',
' gl_FragColor = vec4(glowColor, intensity);',
'}'//vVertexNormal视图坐标系中点的法向量
//viewCameraToVertex视图坐标系中点到摄像机的距离向量
//dot点乘得到它们的夹角的cos值
//从中心向外面角度越来越小(从钝角到锐角)从cos函数也可以知道这个值由负变正,不透明度最终从低到高
效果如图:
Github:https://github.com/StringKun/ThreeJSGlow-Atmosphere