别人的原理 参考自这个文章 。
体积光也是基于后处理,用叠加texture的方式来实现,整个流程如下:
Render the scene, with the light source as white and all occluding objects as black, to a texture.
With a shader, apply a parameterized radial blur to that texture to create the lighting effect
Additively blend that result over a render of the normally lit scene and display on screen
…estimate the probability of occlusion at each pixel by summing samples along a ray to the light source in image space. The proportion of samples that hit the emissive region versus those that strike occluders gives us the desired percentage of occlusion…
我将这个示例的代码下载过来后,升级到threev134版本,经过调试,发现没有光照效果。看起来像是根本没有将这一层叠加上去。
感觉是用了两个EffectComposer的缘故。
光源的光线方向是四散的,我只需要给它做个遮罩模型,只放出来光源球体上的一部分,就可以形成手电筒的效果了。
可以在shadertoy搜索Volumetric Light查看别人做的效果:
Browse (1) - Shadertoy BETA
搜索手电筒Flashlight也可以
我真正使用的原理
这是使用的采样 来实现的。
这样有个缺陷:因为是采样,并非正确的物理光照,因此是没有折射、反射、阴影效果 的。
关键代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 function getVolumetericLightShader ( ) { return { uniforms : { tDiffuse : { value : null }, lightPosition : { value : new THREE .Vector2 (0.5 , 0.5 ) }, exposure : { value : 0.18 }, decay : { value : 0.95 }, density : { value : 0.8 }, weight : { value : 0.4 }, samples : { value : 100 }, }, vertexShader : ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); } ` , fragmentShader : ` // gl_FragCoord是0-1的坐标范围 // vec2 uv = gl_FragCoord.xy/u_resolution; varying vec2 vUv; // 2D纹理采样器 uniform sampler2D tDiffuse; // 纹理中心点 uniform vec2 lightPosition; // 曝光度 uniform float exposure; uniform float decay; uniform float density; uniform float weight; uniform int samples; const int MAX_SAMPLES = 100; void main() { // 当前像素在纹理上的坐标 vec2 texCoord = vUv; // 相对于纹理中心点的坐标,用于确定【采样范围】 vec2 deltaTextCoord = texCoord - lightPosition; // 计算单次采样的步进坐标值:采样数越大,采样点步进坐标就越小 // 因为光束是朝着一个方向发射的,因此这里不需要考虑负数的情况; // 每个点的实际采样范围是一条45度的直线上的samples个点!!! deltaTextCoord *= 1.0 / float(samples) * density; // 采样纹理,获取像素原始颜色,这里第一个参数tDiffuse为null,是采用默认的纹理单元0:GL_TEXTURE0 vec4 color = texture2D(tDiffuse, texCoord); // 亮度衰减 float illuminationDecay = 1.0; // 采样 for(int i=0; i < MAX_SAMPLES; i++) { if(i == samples){ break; } texCoord -= deltaTextCoord; vec4 sampleValue = texture2D(tDiffuse, texCoord); // 给采样点颜色算上亮度衰减系数和权重系数 sampleValue *= illuminationDecay * weight; // 给原始颜色叠加上采样点颜色 color += sampleValue; // 修改亮度衰减系数 illuminationDecay *= decay; } gl_FragColor = color * exposure; } ` , }; }
效果 手电筒 Shader - Shadertoy BETA
这个最简单,帧数也高,用的raymarch
Shader - Shadertoy BETA
Shader - Shadertoy BETA
资料 体积光原理和常用算法 https://zhuanlan.zhihu.com/p/21425792
由这个文章触发的灵感:体积光还可以用于辉光效果
基于分层后处理实现的体积光 https://medium.com/@andrew_b_berg/volumetric-light-scattering-in-three-js-6e1850680a41
参考文章1-基于Three的代码讲解:
(WebGL) Volumetric Light Approximation in Three.js - Blog - (BKcore) Thibaut Despoulain
参考文章2-原理公式讲解:
https://developer.nvidia.com/gpugems/gpugems3/part-ii-light-and-shadows/chapter-13-volumetric-light-scattering-post-process
基于Three.js的实现:
GitHub - hsimpson/threejs-volumetric-spotlight: Volumetric spotlight with three.js