网站怎么做二级页面,做商城网站,做的网站怎么上线,厦门网站建设开发生成阴影贴图的步骤如下#xff1a;
从光位置视点#xff08;阴影相机#xff09;创建深度图。从相机的角度进行屏幕渲染在每个像素点#xff0c;将阴影相机的MVP矩阵计算出的深度值与深度图值进行比较如果深度图值较低#xff0c;则说明该像素点存在阴影 #xff0c;因…生成阴影贴图的步骤如下
从光位置视点阴影相机创建深度图。从相机的角度进行屏幕渲染在每个像素点将阴影相机的MVP矩阵计算出的深度值与深度图值进行比较如果深度图值较低则说明该像素点存在阴影 因此渲染阴影。 1、创建深度贴图
基本上当从头开始做阴影贴图时你只需要准备三件事灯光位置、阴影相机和深度贴图但由于我认为使用可以可视化深度图的ShadowMapViewer更容易理解所以我们将准备灯光 以及在 Three.js 中以常规方式添加阴影。
1.1 定向光
首先制作灯光。
const light new THREE.DirectionalLight( 0xffffff, 1.0 );
// The light is directed from the lights position to the origin of the world coordinates.
light.position.set(-30, 40, 10);scene.add(light);
DirectionalLight 有一个阴影参数因此请附加一个从光源位置查看的阴影相机和一个从阴影相机角度写入深度值的 fbo。
1.2 阴影相机
由于光线是定向的因此使用 OrthographicCamera 作为阴影相机来创建平行投影的深度图。
重要的是必须设置相机范围视锥体以便完全包含要阴影的所有对象。 如果阴影相机范围太宽深度图将不准确因此最好将其设置在尽可能渲染阴影的最低范围不要太宽或太窄以创建漂亮的阴影。
const frustumSize 80;light.shadow.camera new THREE.OrthographicCamera(-frustumSize / 2,frustumSize / 2,frustumSize / 2,-frustumSize / 2,1,80
);// Same position as LIGHT position.
light.shadow.camera.position.copy(light.position);
light.shadow.camera.lookAt(scene.position);
scene.add(light.shadow.camera); 1.3 深度贴图
接下来为阴影相机视点准备深度图。
低分辨率会使图像变得粗糙因此我们这次准备2048 x 2048 fbo。
通常使用 16 位或 32 位纹理因为最好以尽可能高的精度写入深度值但由于 WebGL 尚不兼容尚不支持浮动纹理的设备因为某些设备尚不支持 支持浮点纹理我们将使用 8 位纹理的所有四个通道来存储单个 32 位值在本例中为深度值。
这次我们将使用 Three.js 的 ShaderChunk 来方便转换。
light.shadow.mapSize.x 2048;
light.shadow.mapSize.y 2048;const pars { minFilter: THREE.NearestFilter,magFilter: THREE.NearestFilter, format: THREE.RGBAFormat
};light.shadow.map new THREE.WebGLRenderTarget( light.shadow.mapSize.x, this.light.shadow.mapSize.y, pars );1.4 用于渲染深度图的材质
准备在深度图上写入时要使用的材质。
基本上顶点是一样的深度值是在片段着色器中输入的。
const shadowMaterial new THREE.ShaderMaterial({vertexShader: vertexShader,fragmentShader: shadowFragmentShader
});
顶点着色器
void main(){gl_Position projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
要写入的深度贴图是8位纹理但我们要输入的数据是32位。 我们在two.js的shaderChunk中使用packDepthToRGBA来存储使用rgba通道的深度值。
gl_FragCoord.z 包含从 0 到 1 的深度值因此请按原样输入这些值。
片元着色器
// https://github.com/mrdoob/three.js/blob/master/src/renderers/shaders/ShaderChunk/packing.glsl.js#L18
#include packingvoid main(){// gl_FragCoord.z contains depth values from 0 to 1 in the viewing frustum range of the shadow camera.// 0 for near clip, 1 for far clipgl_FragColor packDepthToRGBA(gl_FragCoord.z);
}
1.5 写入深度值
将 ShadowMaterial 设置为网格并渲染为深度图。
由于需要为阴影相机视点创建深度图因此将深度图指定为“renderTarget”将shadowCamera指定为“camera”。
// update every frame
mesh.material shadowMaterial;
renderer.setRenderTarget(light.shadow.map);
renderer.render(scene, light.shadow.camera);
现在我们有了深度图渲染。准备一个阴影图查看器来查看深度图的外观以进行调试。
// https://threejs.org/examples/?qshadow#webgl_shadowmap_viewerconst depthViewer new ShadowMapViewer(light);
depthViewer.size.set( 300, 300 );...
// render to canvas
renderer.setRenderTarget(null);
depthViewer.render( renderer ); 距离阴影相机越近深度值越小因此结果本质上是相反的但它是检查深度图是否正确渲染的好工具。 2、比较深度并创建阴影
创建深度图后就可以进行屏幕渲染了。
2.1 屏幕渲染材质
准备用于屏幕渲染的材质。
灯光位置和深度图被放入uniform变量中阴影相机投影矩阵和视图矩阵也被放入uniform变量中因为在阴影相机的MVP矩阵中计算的深度也必须在该着色器中计算并与 深度图。
const uniforms {uColor: {value: new THREE.Color(color)},uLightPos: {value: light.position},uDepthMap: {value: light.shadow.map.texture},uShadowCameraP: {value: light.shadow.camera.projectionMatrix},uShadowCameraV: {value: light.shadow.camera.matrixWorldInverse},
}
const material new THREE.ShaderMaterial({vertexShader,fragmentShader,uniforms,
});在顶点着色器中添加一些代码。
uniform mat4 uShadowCameraP;
uniform mat4 uShadowCameraV;varying vec4 vShadowCoord;varying vec3 vNormal;void main(){vNormal normal;vec3 pos position;gl_Position projectionMatrix * viewMatrix * modelMatrix * vec4(pos, 1.0);// Coordinates from the shadow camera viewpoint// Pass to fragment shader and compare with depth map.vShadowCoord uShadowCameraP * uShadowCameraV * modelMatrix * vec4(pos, 1.0);
}vShadowCoord 的结果是剪辑空间中的坐标系因此 vShadowCoord.xyz / vShadowCoord.w 的范围从 (-1, -1, -1) 到 (1,1,1)。如果你想了解更多关于 MVP 矩阵的信息可以点击这里。
vShadowCoord.z / vShadowCoord.w 将是深度值因此让它在 0 和 1 之间转换并将其与深度图进行比较。并让 vShadowCoord.xy / vShadowCoord.w 在 (0, 0) 和 (1, 1) 之间转换为 uv 以引用深度图。
之所以使用MVP矩阵计算得到的结果作为uv是因为我们可以参考与生成深度图的像素同一点的深度值。
由于深度图值是之前通过以 rgba 分布 32 位数据输入的因此在引用时需要将它们恢复为原始值。
此解码使用来自 Three.js 中相同 ShaderChunk 的 unpackRGBAToDepth。片元着色器代码如下
uniform vec3 uColor;
uniform sampler2D uDepthMap;
uniform vec3 uLightPos;varying vec3 vNormal;
varying vec4 vShadowCoord;// https://github.com/mrdoob/three.js/blob/master/src/renderers/shaders/ShaderChunk/packing.glsl.js#L24
#include packingvoid main(){vec3 shadowCoord vShadowCoord.xyz / vShadowCoord.w * 0.5 0.5;float depth_shadowCoord shadowCoord.z;vec2 depthMapUv shadowCoord.xy;float depth_depthMap unpackRGBAToDepth(texture2D(uDepthMap, depthMapUv));// Compare and if the depth value is smaller than the value in the depth map, then there is an occluder and the shadow is drawn.float shadowFactor step(depth_shadowCoord, depth_depthMap);// check the result of the shadow factor.gl_fragColor vec4(vec3(shadowFactor), 1.0);
}
在循环函数中将屏幕渲染过程放在深度图渲染之后
// in the loop function
// Writing into the depth map
mesh.material shaderMaterial;
renderer.setRenderTarget(light.shadow.map);
renderer.render(scene, light.shadow.camera);// put a material for screen rendering and render it to canvas.
mesh.material material;
renderer.setRenderTarget(null);
renderer.render(scene, camera);
2.2 调整深度值比较 当显示shadowFactor比较深度值的结果时会生成阴影但会创建额外的图案。这称为阴影痘痘shadow acne必须通过减去考虑到这一点的偏差来比较深度值。片元着色器如下
void main(){...float cosTheta dot(normalize(uLightPos), vNormal);float bias 0.005 * tan(acos(cosTheta)); // cosTheta is dot( n,l ), clamped between 0 and 1bias clamp(bias, 0.0, 0.01);float shadowFactor step(depth_shadowCoord - bias, depth_depthMap);gl_fragColor vec4(vec3(shadowFactor), 1.0);
}痘痘消失了。
阴影区域不干净但定向光过程可以将其覆盖所以我将保持原样。影子相机视锥体的外侧被切掉了所以我只处理那部分。片元着色器代码如下
void main(){// Assume no shadow except for viewing frustum.// https://github.com/mrdoob/three.js/blob/master/src/renderers/shaders/ShaderChunk/packing.glsl.js#L24bvec4 inFrustumVec bvec4 ( shadowCoord.x 0.0, shadowCoord.x 1.0, shadowCoord.y 0.0, shadowCoord.y 1.0 );bool inFrustum all( inFrustumVec );bvec2 frustumTestVec bvec2( inFrustum, shadowCoord.z 1.0 );bool frustumTest all( frustumTestVec );if(frustumTest false){shadowFactor 1.0;}float difLight max(0.0, cosTheta);float shading shadowFactor * difLight;gl_fragColor vec4(vec3(shading), 1.0);
}
乘以定向光然后完成着色。 应用于基于着色的 uColor。片元着色器如下
void main(){color mix(uColor - 0.1, uColor 0.1, shading);gl_fragColor vec4(color, 1.0);
} 3、额外的方法 - 剔除正面
我还将向拟展示如何无偏差地绘制阴影。
渲染深度图时仅渲染背面网格而在屏幕渲染期间渲染正面网格。
这种方法不需要拉偏因为痘痘不会出现。但是只能使用闭合网格因此如果放置平面则不会创建其阴影。在此演示中此方法有效因为只有闭合网格。
const shaderMaterial new THREE.ShaderMaterial({vertexShader: vertexShader,fragmentShader: shadowFragmentShader,side: THREE.BackSide
});
片元着色器
void main(){vec3 shadowCoord (vShadowCoord.xyz / vShadowCoord.w * 0.5 0.5);float depth_shadowCoord shadowCoord.z;float depth_depthMap unpackRGBAToDepth(texture2D(uDepthMap, shadowCoord.xy));// Acne does not arise, so no bias is required.float shadowFactor step(depth_shadowCoord, depth_depthMap);...
} 这是将正确的对象从盒子替换为平面的比较。
对于其他封闭物体没有区别但是对于平面来说剔除正面的方法对于阴影不起作用。