ThreeJS 物理材质shader源码分析(顶点着色器)

再此之前推荐一款GLTF物理材质在线编辑器https://tinygltf.xyz/

ThreeJS 物理材质shader源码分析(顶点着色器)

Threejs将shader代码分为ShaderLib和ShaderChunk两部分,ShaderLib通过组合ShaderChunk的代码来构建vertexShader和fragmentShader。下面主要分析物理材质的shader源码,他主要的两个文件在shaderLib里面的meshphysical_vert.glsl和meshphysical_frag.glsl,前面的文件是顶点着色器,后者是像素着色器

1.顶点着色器meshphysical_vert.glsl

#define PHYSICAL            // 定义物理材质的宏

varying vec3 vViewPosition; // 相机空间每个顶点的位置

#ifndef FLAT_SHADED

varying vec3 vNormal;   // 相机空间法线方向

#endif

#include <common>           // 包含着色器公共模块(包含常用的数学工具函数以及一些常量定义什么的)

#include <uv_pars_vertex>   // 包含处理uv所需要的一些定义

#include <uv2_pars_vertex>  // 包含处理uv2所需要的一些定义

#include <displacementmap_pars_vertex> // 包含置换贴图displacementmap所需要的定义

#include <color_pars_vertex>            // 包含顶点颜色所需要的定义

#include <fog_pars_vertex>              // 包含雾化效果所需要的定义

#include <morphtarget_pars_vertex>      // 包含变形动画所需要的定义

#include <skinning_pars_vertex>         // 包含蒙皮动画所需要的定义

#include <shadowmap_pars_vertex>        // 包含阴影计算所需要的定义

#include <logdepthbuf_pars_vertex>      // 包含深度处理的一些定义

#include <clipping_planes_pars_vertex>  // 包含裁剪平面所需要的一些定义

void main() {

#include <uv_vertex>            // uv 数据处理

#include <uv2_vertex>           // uv2 数据处理

#include <color_vertex>         // 颜色 数据处理

#include <beginnormal_vertex>   // 开始法线处理

#include <morphnormal_vertex>   // 变形动画法线处理

#include <skinbase_vertex>      // 骨骼蒙皮基本运算

#include <skinnormal_vertex>    // 骨骼蒙皮法线运算

#include <defaultnormal_vertex> // 默认法线处理

#ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED

vNormal = normalize( transformedNormal );

#endif

#include <begin_vertex>         // 开始顶点位置处理

#include <morphtarget_vertex>   // 变形动画位置处理

#include <skinning_vertex>          // 蒙皮顶点处理

#include <displacementmap_vertex>   // 置换贴图运用顶点处理

#include <project_vertex>           // 投影顶点运算

#include <logdepthbuf_vertex>       // logDepth深度运算

#include <clipping_planes_vertex>   // 裁剪平面运算

vViewPosition = - mvPosition.xyz;

#include <worldpos_vertex>      // 世界坐标运算

#include <shadowmap_vertex>     // 阴影所需要的一些运算

#include <fog_vertex>           // 雾化所需要的运算

}

下面讲解这个顶点着色器包含的shaderTrunk

Common.glsl

#define PI 3.14159265359 //π(180)

#define PI2 6.28318530718 //2π(360)

#define PI_HALF 1.5707963267949 //0.5π(90度)

#define RECIPROCAL_PI 0.31830988618 // 1/π

#define RECIPROCAL_PI2 0.15915494    // 1/2π

#define LOG2 1.442695           // log以2为底e的对数 log2E

#define EPSILON 1e-6        // 1x10的-6次方 相当于接近0的小数

#define saturate(a) clamp( a, 0.0, 1.0 )        // 收拢到0~1.0之间

#define whiteCompliment(a) ( 1.0 - saturate( a ) ) // 收拢后再用1.0减

// 上面定义全局常量

float pow2( const in float x ) { return x*x; }    // 平方

float pow3( const in float x ) { return x*x*x; }    // 三次方

float pow4( const in float x ) { float x2 = x*x; return x2*x2; } // 四次方

float average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); } // 三向量x,y,z三个数平均数

// expects values in the range of [0,1]x[0,1], returns values in the [0,1] range.

// do not collapse into a single function per: http://byteblacksmith.com/improvements-to-the-canonical-one-liner-glsl-rand-for-opengl-es-2-0/

highp float rand( const in vec2 uv ) {      // 随机数

const float a = 12.9898, b = 78.233, c = 43758.5453;

float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );

return fract(sin(sn) * c);

}

// 入射光

struct IncidentLight {

vec3 color;

vec3 direction;

bool visible;

};

// 反射光

struct ReflectedLight {

vec3 directDiffuse;

vec3 directSpecular;

vec3 indirectDiffuse;

vec3 indirectSpecular;

};

// 几何体信息

struct GeometricContext {

vec3 position;

vec3 normal;

vec3 viewDir;

};

// 变换方向

vec3 transformDirection( in vec3 dir, in mat4 matrix ) {

return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );

}

// 逆向变换方向(一般知道worldmatrix 和 local下的normal求worldnormal可以用次方法)

// http://en.wikibooks.org/wiki/GLSL_Programming/Applying_Matrix_Transformations

vec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {

return normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );

}

// 点相对平面做投影

vec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {

float distance = dot( planeNormal, point - pointOnPlane );

return - distance * planeNormal + point;

}

// 判断点在平面哪一边

float sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {

return sign( dot( point - pointOnPlane, planeNormal ) );

}

// 线个平面相交点

vec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {

return lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;

}

// 矩阵求转置

mat3 transposeMat3( const in mat3 m ) {

mat3 tmp;

tmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );

tmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );

tmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );

return tmp;

}

// 线性rgb颜色值求相对亮度

// https://en.wikipedia.org/wiki/Relative_luminance

float linearToRelativeLuminance( const in vec3 color ) {

vec3 weights = vec3( 0.2126, 0.7152, 0.0722 );

return dot( weights, color.rgb );

}

uv数据处理

涉及的ShaderTrunk文件有uv_pars_vertex.glsl,uv_vertex.glsl

uv_pars_vertex.glsl

// 如果有定义这几种贴图就需要包含这些变量定义

#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )

varying vec2 vUv;           // 输出到fragmentshader变量定义

uniform mat3 uvTransform;   // uv变换矩阵3x3

#endif

uv_vertex.glsl

// 如果定义了几种贴图才运用下面代码

#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )

vUv = ( uvTransform * vec3( uv, 1 ) ).xy; // uv运用变换矩阵后输出到fragmentshader

#endif

uv2数据处理

uv2:顶点的第二套uv坐标

涉及的文件有uv2_pars_vertex.glsl,uv2_vertex.glsl

uv2_pars_vertex.glsl

// 如果定义了USE_LIGHTMAP和USE_AOMAP才加入下面代码,也就是光照贴图和AO贴图都需要用到第二套uv

#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )

attribute vec2 uv2; // 定义顶点的uv2属性

varying vec2 vUv2; // 定义输出到fragmentshader uv2变量

#endif

uv2_vertex.glsl

// 如果定义USE_LIGHTMAP或者USE_AOMAP加入下面代码

#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )

vUv2 = uv2; // 直接将顶点的第二套uv输出到fragmentshader

#endif

顶点颜色数据处理

涉及的ShaderTrunk文件有color_pars_vertex.glsl,color_vertex.glsl

color_pars_vertex.glsl

// 如果定义了USE_COLOR宏才加入下面代码

#ifdef USE_COLOR

varying vec3 vColor; // 定义将颜色输出到fragmentshader的变量

#endif

color_vertex.glsl

// 如果定义了使用颜色变量才加入这些代码

#ifdef USE_COLOR

vColor.xyz = color.xyz; // 将输入的顶点颜色不经过任何处理直接输出到fragmentshader

#endif

置换贴图相关运算

涉及ShaderTrunk文件有displacementmap_pars_vertex.glsl, displacementmap_ vertex.glsl

displacementmap_pars_vertex.glsl

// 如果定义了USE_DISPLACEMENTMAP则加入下面代码

#ifdef USE_DISPLACEMENTMAP

uniform sampler2D displacementMap;  // 置换贴图采样器

uniform float displacementScale;    // 缩放参数

uniform float displacementBias;     // bias因子

#endif

displacement_vertex.glsl

// 如果定义了USE_DISPLACEMENTMAP加入下面代码

#ifdef USE_DISPLACEMENTMAP

// 下面的算法主要目的是将顶点按照法线方向偏移一定距离,这个距离的控制通过采样置换贴图的值(dx),distance = dx*dscale+dbias;

transformed += normalize( objectNormal ) * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias );

#endif

雾化处理

涉及的ShaderTrunk文件fog_vars_vertex.glsl,fog_vertex.glsl

fog_vars_vertex.glsl

// 如果定义USE_FOG加入代码

#ifdef USE_FOG

varying float fogDepth; // 输入给fragmentshader的雾化深度变量定义

#endif

fog_vertex.glsl

// 如果定义的USE_FOG加入代码

#ifdef USE_FOG

fogDepth = -mvPosition.z; // 将摄像机空间的顶点z坐标赋值给fogDepth

#endif

变形动画相关处理

涉及的shaderTrunk文件morphtarget_pars_vertex.glsl, morphnormal_vertex.glsl, morphtarget_vertex.glsl

morphtarget_pars_vertex.glsl

// 如果定义了USE_MORPHTARGETS加入下面代码

#ifdef USE_MORPHTARGETS

#ifndef USE_MORPHNORMALS

uniform float morphTargetInfluences[ 8 ];// 变形动画调节因子

#else

uniform float morphTargetInfluences[ 4 ];

#endif

#endif

morphnormal_vertex.glsl

// 如果定义了USE_MORPHNORMALS加入代码

#ifdef USE_MORPHNORMALS

// 下面线性插值将法线进行调节 c = (b - a) * scale +a;

objectNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];

objectNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];

objectNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];

objectNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];

#endif

morphtarget_vertex.glsl

// 如果定义了USE_MORPHTARGETS

#ifdef USE_MORPHTARGETS

// 对位置进行线性插值后加权 c = (b - a)*scale+a;

transformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];

transformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];

transformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];

transformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];

#ifndef USE_MORPHNORMALS // 如果没有定义USE_MORPHNORMALS才执行如下操作

transformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];

transformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];

transformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];

transformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];

#endif

#endif

骨骼动画

涉及的shaderTrunk文件有skinning_pars_vertex.glsl, skinbase_vertex.glsl, skinnormal_vertex.glsl, skinning_vertex.glsl

skinning_pars_vertex.glsl

// 如果定义了USE_SKINNING加入代码

#ifdef USE_SKINNING

uniform mat4 bindMatrix; // 绑定矩阵

uniform mat4 bindMatrixInverse; // 绑定矩阵逆矩阵

#ifdef BONE_TEXTURE // 如果使用了骨骼纹理(定义了BONE_TEXTURE)

uniform sampler2D boneTexture; // 骨骼纹理 这里使用的float纹理

uniform int boneTextureSize;   // 骨骼纹理大小

mat4 getBoneMatrix( const in float i ) { // 获取骨骼纹理

float j = i * 4.0; // 每个像素是xyzw一个向量 四个就能组合一个像素,这里i表示骨骼id(0~xxx)

float x = mod( j, float( boneTextureSize ) ); // 计算骨骼数据所在的x坐标

float y = floor( j / float( boneTextureSize ) ); // 计算骨骼数据所在的y坐标

float dx = 1.0 / float( boneTextureSize );  // 每个像素占宽度所在的百分比

float dy = 1.0 / float( boneTextureSize );  // 每个像素占宽度所在的百分比

y = dy * ( y + 0.5 );

// 采样骨骼矩阵

vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );

vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );

vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );

vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );

mat4 bone = mat4( v1, v2, v3, v4 );

return bone;

}

#else

uniform mat4 boneMatrices[ MAX_BONES ];

// 直接通过统一变量存储骨骼矩阵获取数据,这种方式可能受限uniform存储的容量限制导致支持骨骼数量不会太多

mat4 getBoneMatrix( const in float i ) {

mat4 bone = boneMatrices[ int(i) ];

return bone;

}

#endif

#endif

skinbase_vertex.glsl

// 如果定义了USE_SKINNING  获取四个骨骼矩阵

#ifdef USE_SKINNING

mat4 boneMatX = getBoneMatrix( skinIndex.x );

mat4 boneMatY = getBoneMatrix( skinIndex.y );

mat4 boneMatZ = getBoneMatrix( skinIndex.z );

mat4 boneMatW = getBoneMatrix( skinIndex.w );

#endif

skinnormal_vertex.glsl

// 如果定义了USE_SKINNING使用下面代码计算骨骼对法线的影响

#ifdef USE_SKINNING

// 对四个骨骼矩阵进行加权

mat4 skinMatrix = mat4( 0.0 );

skinMatrix += skinWeight.x * boneMatX;

skinMatrix += skinWeight.y * boneMatY;

skinMatrix += skinWeight.z * boneMatZ;

skinMatrix += skinWeight.w * boneMatW;

// 应用法线和蒙皮矩阵

skinMatrix  = bindMatrixInverse * skinMatrix * bindMatrix;

objectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;

#endif

skinning_vertex.glsl

// 如果定义USE_SKINNING加入代码

#ifdef USE_SKINNING

// 使用四个骨骼矩阵对顶点坐标进行加权

vec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );

vec4 skinned = vec4( 0.0 );

skinned += boneMatX * skinVertex * skinWeight.x;

skinned += boneMatY * skinVertex * skinWeight.y;

skinned += boneMatZ * skinVertex * skinWeight.z;

skinned += boneMatW * skinVertex * skinWeight.w;

transformed = ( bindMatrixInverse * skinned ).xyz;

#endif

阴影计算shadowmap

涉及的shaderTrunk的代码文件shadowmap_pars_vertex.glsl, shadowmap_vertex.glsl

shadowmap_pars_vertex.glsl

// 如果定义了USE_SHADOWMAP加入下面代码

#ifdef USE_SHADOWMAP

#if NUM_DIR_LIGHTS > 0

uniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHTS ];// 方向光的投影矩阵

varying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];// 输出到fragmentshader的坐标

#endif

#if NUM_SPOT_LIGHTS > 0

uniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHTS ];// 聚光灯的阴影矩阵

varying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];// 输出到fragmentshader的坐标

#endif

#if NUM_POINT_LIGHTS > 0

uniform mat4 pointShadowMatrix[ NUM_POINT_LIGHTS ];// 点光源的阴影矩阵

varying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ]; // 点光源的阴影坐标

#endif

/*

#if NUM_RECT_AREA_LIGHTS > 0

// TODO (abelnation): uniforms for area light shadows

#endif

*/

#endif

shadowmap_vertex.glsl

// 如果定义了USE_SHADOWMAP加入代码 通过个光源的阴影投影矩阵对顶点坐标进行投影

#ifdef USE_SHADOWMAP

#if NUM_DIR_LIGHTS > 0

#pragma unroll_loop

for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {

vDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;

}

#endif

#if NUM_SPOT_LIGHTS > 0

#pragma unroll_loop

for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {

vSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;

}

#endif

#if NUM_POINT_LIGHTS > 0

#pragma unroll_loop

for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {

vPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;

}

#endif

/*

#if NUM_RECT_AREA_LIGHTS > 0

// TODO (abelnation): update vAreaShadowCoord with area light info

#endif

*/

#endif

logDepth深度运算

涉及shaderTrunk文件logdepthbuf_pars_vertex.glsl, logdepthbuf_vertex.glsl

logdepthbuf_pars_vertex.glsl

// 如果定义了USE_LOGDEPTHBUF加入下面代码

#ifdef USE_LOGDEPTHBUF

#ifdef USE_LOGDEPTHBUF_EXT// 如果定义了USE_LOGDEPTHBUF_EXT

varying float vFragDepth; // 输出到fragmentshader的深度

#endif

uniform float logDepthBufFC;

#endif

logdepthbuf_vertex.glsl

// 如果定义了USE_LOGDEPTHBUF

#ifdef USE_LOGDEPTHBUF

#ifdef USE_LOGDEPTHBUF_EXT// 如果定义了USE_LOGDEPTHBUF_EXT

vFragDepth = 1.0 + gl_Position.w;

#else

gl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;

gl_Position.z *= gl_Position.w;

#endif

#endif

裁剪平面运算

涉及的shaderTrunk代码clipping_planes_pars_vertex.glsl, clipping_planes_vertex.glsl

clipping_planes_pars_vertex.glsl

// 如果裁剪平面数量大于0 且没有定义PHYSICAL和PHONG

#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )

varying vec3 vViewPosition;// 输出到摄像机空间的位置

#endif

clipping_planes_vertex.glsl

// 如果裁剪平面数量大于0 且没有定义PHYSICAL和PHONG

#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )

vViewPosition = - mvPosition.xyz;// 摄像机空间的位置

#endif

顶点投影变换

涉及shaderTrunk的文件project_vertex.glsl

project_vertex.glsl

vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 ); // 将local坐标转换到摄像机空间

gl_Position = projectionMatrix * mvPosition; // 再将摄像机空间坐标乘以投影矩阵做投影变换转到裁剪空间

顶点世界坐标变换

涉及的shaderTrunk文件

worldpos_vertex.glsl

// 如果定义USE_ENVMAP DISTANCE USE_SHADOWMAP就需要计算顶点相对世界坐标系下的位置

#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )

vec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );

#endif

基本的法线处理

涉及的shaderTrunk文件beginnormal_vertex.glsl,defaultnormal_vertex.glsl

beginnormal_vertex.glsl

vec3 objectNormal = vec3( normal );// 将顶点输入的法线临时存储一个变量

defaultnormal_vertex.glsl

// 将local坐标下的法线转换到摄像机坐标系下

vec3 transformedNormal = normalMatrix * objectNormal;

#ifdef FLIP_SIDED   // 如果定义了FLIP_SIDED 翻转法线

transformedNormal = - transformedNormal;

#endif

ThreeJS 物理材质shader源码分析(顶点着色器)

上一篇:如何创建Asp.net MVC ViewModel


下一篇:java中String类型变量的赋值问题