简介
如果在光源处放置一位观察者,其视线方向与光线一致,那么观察者也看不到阴影。他看到的每一处都在光的照射下,而那些背后的,他没有看到的物体则处在阴影中。这里,我们需要用到光源与物体之间的距离(实际上也就是物体在光源坐标系下的深度z值)来决定物体是否可见。
可以使用两对着色器以实现阴影
- 一对着色器用来计算光源到物体的距离。
- 另一对着色器根据[1]中计算出的距离绘制场景。
- 使用一张纹理图像把[1]的结果传入[2]中,这张纹理图像就被称为阴影贴图(shadow map),而通过阴影贴图实现阴影的方法就被称为阴影映射(shadow mapping)。
如何引入阴影
通过分析,一个点不在阴影里的条件是,光源、相机能同时看到点,光源看不到、相机能看到的,就是阴影区域。 第一步:从光源位置出发找出可见点,记录光源可见点的深度,得到光源深度图。 第二步:从相机出发,找可见点,如果点可见,坐标变换求此点到光源的距离。如果此距离与光源深度图中此位置的深度一致,说明此点可以被光源照到。是为光源、相机能同时看到点;如果不一致,说明是阴影点。
改造initPipeline 增加阴影管线
// 抽离公用配置 ///////////////////////////////////////////////////////
//顶点缓冲区配置
const vertexBuffers = [{
arrayStride: 8 * 4,
attributes: [
{
shaderLocation: 0,
offset: 0,
format: 'float32x3',
},
{
shaderLocation: 1,
offset: 3 * 4,
format: 'float32x3',
},
{
shaderLocation: 2,
offset: 6 * 4,
format: 'float32x2',
},
]
}]
//图元的状态
const primitive = {
topology: 'triangle-list',
cullMode: 'back'
}
//深度模板
const depthStencil = {
depthWriteEnabled: true,
depthCompare: 'less',
format: 'depth32float',
}
//一、投影管线///////////////////////////////////////////
const shadowPipeline = await device.createRenderPipelineAsync({
label: 'Shadow Pipline',
layout: 'auto',
vertex: {
module: device.createShaderModule({
code: shadowDepth,
}),
entryPoint: 'main',
buffers: vertexBuffers
},
primitive, depthStencil
})
//投影缓存
const shadowDepthTexture = device.createTexture({
size: [2048, 2048],
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
format: 'depth32float'
});
const shadowDepthView = shadowDepthTexture.createView()
//二、渲染管线///////////////////////////////////////////
const renderPipeline = await device.createRenderPipelineAsync({
label: 'Render Pipline',
layout: 'auto',
vertex: {
module: device.createShaderModule({
code: shadowVertex,
}),
entryPoint: 'main',
buffers: vertexBuffers
},
fragment: {
module: device.createShaderModule({
code: shadowFrag,
}),
entryPoint: 'main',
targets: [
{
format: format
}
]
},
primitive, depthStencil
})
//深度缓存
const renderDepthTexture = device.createTexture({
size, format: 'depth32float',
usage: GPUTextureUsage.RENDER_ATTACHMENT
})
const renderDepthView = renderDepthTexture.createView()
//顶点数据缓冲区//////////////////////////////////////
// 加入了立方体,球形两种模型 参考index渲染
//mvp矩阵、灯光、阴影 ////////////////////////////////////////////////////////
const modelViewBuffer = device.createBuffer({
label: 'GPUBuffer store n*4x4 matrix',
size: 4 * 4 * 4 * NUM, // 4 x 4 x float32 x NUM
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
})
const cameraProjectionBuffer = device.createBuffer({
label: 'GPUBuffer for camera projection',
size: 4 * 4 * 4, // 4 x 4 x float32
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
})
//灯光投影
const lightProjectionBuffer = device.createBuffer({
label: 'GPUBuffer for light projection',
size: 4 * 4 * 4, // 4 x 4 x float32
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
})
// 颜色
const colorBuffer = device.createBuffer({
label: 'GPUBuffer store n*4 color',
size: 4 * 4 * NUM, // 4 x float32 x NUM
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
})
//灯光
const lightBuffer = device.createBuffer({
label: 'GPUBuffer store 4x4 matrix',
size: 4 * 4, // 4 x float32: position vec4
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
})
//绑定组////////////////////////////////////////////////////
//模型矩阵、视图矩阵、灯光投影、颜色
const vsGroup = device.createBindGroup({
label: 'Group for renderPass',
layout: renderPipeline.getBindGroupLayout(0),
entries: [
{
binding: 0,
resource: {
buffer: modelViewBuffer
}
},
{
binding: 1,
resource: {
buffer: cameraProjectionBuffer
}
},
{
binding: 2,
resource: {
buffer: lightProjectionBuffer
}
},
{
binding: 3,
resource: {
buffer: colorBuffer
}
}
]
})
//灯光、阴影
const fsGroup = device.createBindGroup({
label: 'Group for fragment',
layout: renderPipeline.getBindGroupLayout(1),
entries: [
{
binding: 0,
resource: {
buffer: lightBuffer
}
},
{
binding: 1,
resource: shadowDepthView
},
{
binding: 2,
resource: device.createSampler({
compare: 'less',
})
}
]
})
//投影管线组
const shadowGroup = device.createBindGroup({
label: 'Group for shadowPass',
layout: shadowPipeline.getBindGroupLayout(0),
entries: [{
binding: 0,
resource: {
buffer: modelViewBuffer
}
}, {
binding: 1,
resource: {
buffer: lightProjectionBuffer
}
}]
})
改造 draw 绘制阴影
//绘制阴影管线,输出阴影贴图 pipelineObj.shadowDepthView
{
const shadowPassDescriptor = {
colorAttachments: [],
depthStencilAttachment: {
view: pipelineObj.shadowDepthView,
depthClearValue: 1.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
}
}
// 阴影通道的编码过程
const shadowPass = commandEncoder.beginRenderPass(shadowPassDescriptor)
// 使用阴影渲染管线
shadowPass.setPipeline(pipelineObj.shadowPipeline)
shadowPass.setBindGroup(0, pipelineObj.shadowGroup)
// set box vertex
shadowPass.setVertexBuffer(0, pipelineObj.boxBuffer.vertex)
shadowPass.setIndexBuffer(pipelineObj.boxBuffer.index, 'uint16')
shadowPass.drawIndexed(box.indexCount, 2, 0, 0, 0)
// set sphere vertex
shadowPass.setVertexBuffer(0, pipelineObj.sphereBuffer.vertex)
shadowPass.setIndexBuffer(pipelineObj.sphereBuffer.index, 'uint16')
shadowPass.drawIndexed(sphere.indexCount, NUM - 2, 0, 0, 2)
shadowPass.end()
}
// 绘制渲染管线
{
const renderPassDescriptor = {
colorAttachments: [
{
view: context.getCurrentTexture().createView(),
clearValue: { r: 0, g: 0, b: 0, a: 1.0 },
loadOp: 'clear',
storeOp: 'store'
}
],
depthStencilAttachment: {
view: pipelineObj.renderDepthView,
depthClearValue: 1.0,
depthLoadOp: 'clear',
depthStoreOp: 'store',
}
}
// 渲染通道编码过程
const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor)
// 使用常规渲染管线
renderPass.setPipeline(pipelineObj.renderPipeline)
renderPass.setBindGroup(0, pipelineObj.vsGroup)
renderPass.setBindGroup(1, pipelineObj.fsGroup)
// set box vertex
renderPass.setVertexBuffer(0, pipelineObj.boxBuffer.vertex)
renderPass.setIndexBuffer(pipelineObj.boxBuffer.index, 'uint16')
renderPass.drawIndexed(box.indexCount, 2, 0, 0, 0)
// set sphere vertex
renderPass.setVertexBuffer(0, pipelineObj.sphereBuffer.vertex)
renderPass.setIndexBuffer(pipelineObj.sphereBuffer.index, 'uint16')
renderPass.drawIndexed(sphere.indexCount, NUM - 2, 0, 0, 2)
renderPass.end()
}
改造运行时函数
const NUM = 3;//设置3个模型,地板用于接收阴影,一个正方体、一个球形。
const scene = []
const modelViewMatrix = new Float32Array(NUM * 4 * 4)
const colorBuffer = new Float32Array(NUM * 4)
// 添加正方体
{
const position = { x: 0, y: 5, z: -20 }
const rotation = { x: 0, y: Math.PI / 4, z: 0 }
const scale = { x: 5, y: 5, z: 5 }
const modelView = getModelViewMatrix(position, rotation, scale)
modelViewMatrix.set(modelView, 0 * 4 * 4)
// 颜色
colorBuffer.set([0.5, 0.5, 0.5, 1], 0 * 4)
scene.push({ position, rotation, scale })
}
// 添加地板
{
const position = { x: 0, y: -10, z: -20 }
const rotation = { x: 0, y: 0, z: 0 }
const scale = { x: 50, y: 0.5, z: 40 }
const modelView = getModelViewMatrix(position, rotation, scale)
modelViewMatrix.set(modelView, 1 * 4 * 4)
// 颜色
colorBuffer.set([1, 1, 1, 1], 1 * 4)
scene.push({ position, rotation, scale })
}
// 添加球体
{
const position = { x: 0, y: 5, z: -5 }
const rotation = { x: 0, y: 0, z: 0 }
const scale = { x: 0.5, y: 0.5, z: 0.5 }
const modelView = getModelViewMatrix(position, rotation, scale)
modelViewMatrix.set(modelView, 2 * 4 * 4)
// 颜色
colorBuffer.set([1, 0, 1, 1], 2 * 4)
scene.push({ position, rotation, scale, y: position.y, v: 0 })
}
// 设置模型矩阵、与颜色
device.queue.writeBuffer(pipelineObj.colorBuffer, 0, colorBuffer)
device.queue.writeBuffer(pipelineObj.modelViewBuffer, 0, modelViewMatrix)
// 动态设置平行光坐标,
const lightPosition = vec3.fromValues(0, 100, 0)//灯光坐标
const lightViewMatrix = mat4.create()
const lightProjectionMatrix = mat4.create()
const up = vec3.fromValues(0, 1, 0)
const origin = vec3.fromValues(0, 0, 0)
function frame() {
// 更新灯光坐标
const now = performance.now()
lightPosition[0] = Math.sin(now / 1500) * 50
lightPosition[2] = Math.cos(now / 1500) * 50
// 更新 lvp 矩阵
mat4.lookAt(
lightViewMatrix,
lightPosition,
origin, up
)
mat4.ortho(lightProjectionMatrix, -40, 40, -40, 40, -50, 200)
mat4.multiply(lightProjectionMatrix, lightProjectionMatrix, lightViewMatrix)
device.queue.writeBuffer(pipelineObj.lightProjectionBuffer, 0, lightProjectionMatrix)//设置灯光投影矩阵
device.queue.writeBuffer(pipelineObj.lightBuffer, 0, lightPosition)//设置灯光坐标
...
shader 修改
阴影管线
阴影管线只输出,阴影深度贴图,不渲染。只有顶点着色器
export const shadowDepth = `@group(0) @binding(0) var<storage> modelViews : array<mat4x4<f32>>;
@group(0) @binding(1) var<uniform> lightProjection : mat4x4<f32>;
@vertex
fn main(
@builtin(instance_index) index : u32,
@location(0) position : vec3<f32>,
@location(1) normal : vec3<f32>,
@location(2) uv : vec2<f32>,
) -> @builtin(position) vec4<f32> {
let modelview = modelViews[index];
let pos = vec4(position, 1.0);
return lightProjection * modelview * pos;
}
`
渲染管线
// 顶点着色器 /////////////////////////////////////
// 增加了顶点的,模型坐标与法向量
output.fragPosition = (modelview * pos).xyz;
output.fragNormal = (modelview * vec4<f32>(normal, 0.0)).xyz;
// 最重要阴影坐标
output.shadowPos = vec3<f32>(posFromLight.xy * vec2<f32>(0.5, -0.5) + vec2<f32>(0.5, 0.5), posFromLight.z);
// 片源着色器 /////////////////////////////////////
let diffuse: f32 = max(dot(normalize(lightPosition.xyz), fragNormal), 0.0);
var shadow : f32 = 0.0;
for (var y : i32 = -1 ; y <= 1 ; y = y + 1) {
for (var x : i32 = -1 ; x <= 1 ; x = x + 1) {
let offset = vec2<f32>(f32(x) / size, f32(y) / size);
shadow = shadow + textureSampleCompare(
shadowMap,
shadowSampler,
shadowPos.xy + offset,
shadowPos.z - 0.005
);
}
}
shadow = shadow / 9.0;//球平均
//输出片源颜色
let lightFactor = min(0.3 + shadow * diffuse, 1.0);
return vec4<f32>(objectColor * lightFactor, 1.0);