Fog
Fog is an easy effect that helps manage the complexity of your scene - as things that are very far away can be made to `fade` into the distance - instead of either 'popping' due to the far plane clipping. You can add it in by using the 'depth' information you've stored in the depth buffer.
The calculated z-distances can be used as a 'fog' factor - as objects get further away they're made transparent or blending with the background color (so they disapear).
Functions Used: setVertexBuffer(), setIndexBuffer(), drawIndexed(), createBuffer(), getMappedRange(), getContext(), requestAdapter(), getPreferredCanvasFormat(), createCommandEncoder(), beginRenderPass(), setPipeline(), draw(), end(), submit(), getCurrentTexture(), createView(), createShaderModule()
For example purposes - we use the calculated depth (z-distance) and use it as the lighting color for the shape. As geometry gets further away into the distance its z value increase and it becomes more white.
<?php
sphere = function()
{
// ------------------------------------------------
function geo ()
{
let widthSegments = 10 ,
heightSegments = 10 ,
radius = 1.0 ,
phiStart = 0 , phiLength = Math . PI * 2 ,
thetaStart = 0 , thetaLength = Math . PI ;
widthSegments = Math . max ( 3 , Math . floor ( widthSegments ) );
heightSegments = Math . max ( 2 , Math . floor ( heightSegments ) );
const thetaEnd = Math . min ( thetaStart + thetaLength , Math . PI );
let index = 0 ;
const grid = [];
const vertex = { x : 0 , y : 0 , z : 0 };
const normal = { x : 0 , y : 0 , z : 0 };
// buffers
const mesh = { v :[], n :[], t :[], i :[] }
// generate vertices, normals and uvs
for ( let iy = 0 ; iy <= heightSegments ; iy ++ ) {
const verticesRow = [];
const v = iy / heightSegments ;
// special case for the poles
let uOffset = 0 ;
if ( iy === 0 && thetaStart === 0 ) {
uOffset = 0.5 / widthSegments ;
} else if ( iy === heightSegments && thetaEnd === Math . PI ) {
uOffset = - 0.5 / widthSegments ;
}
for ( let ix = 0 ; ix <= widthSegments ; ix ++ ) {
const u = ix / widthSegments ;
// vertex
vertex . x = - radius * Math . cos ( phiStart + u * phiLength ) * Math . sin ( thetaStart + v * thetaLength );
vertex . y = radius * Math . cos ( thetaStart + v * thetaLength );
vertex . z = radius * Math . sin ( phiStart + u * phiLength ) * Math . sin ( thetaStart + v * thetaLength );
mesh . v . push ( [ vertex . x , vertex . y , vertex . z ] );
// normal
let vl = vertex . x * vertex . x + vertex . y * vertex . y + vertex . z * vertex . z ;
normal . x = vertex . x / vl ;
normal . y = vertex . y / vl ;
normal . z = vertex . z / vl ;
mesh . n . push ( [ normal . x , normal . y , normal . z ] );
// uv
mesh . t . push ( [ u + uOffset , v ] );
verticesRow . push ( index ++ );
}
grid . push ( verticesRow );
}
// indices
for ( let iy = 0 ; iy < heightSegments ; iy ++ ) {
for ( let ix = 0 ; ix < widthSegments ; ix ++ ) {
const a = grid [ iy ][ ix + 1 ];
const b = grid [ iy ][ ix ];
const c = grid [ iy + 1 ][ ix ];
const d = grid [ iy + 1 ][ ix + 1 ];
if ( iy !== 0 || thetaStart > 0 ) mesh . i . push ( a , b , d );
if ( iy !== heightSegments - 1 || thetaEnd < Math . PI ) mesh . i . push ( b , c , d );
}
}
return mesh ;
}
// -------------------------------
this . create = async function( device , presentationFormat , presentationSize , textureName = "https://webgpulab.xbdev.net/var/images/earth.jpg" )
{
let mesh = geo ();
console . log ( 'num Vertices:' , mesh . v . length );
console . log ( 'num Coords:' , mesh . t . length );
console . log ( 'num Normals:' , mesh . n . length );
console . log ( 'num Indices:' , mesh . i . length );
// -------------------------------------------------
// Lets biuld the buffers to hold the data
if ( mesh . n . length == 0 ) mesh . n . length = mesh . v . length * 3 ;
if ( mesh . t . length == 0 ) mesh . t . length = mesh . v . length * 2 ;
mesh . i = mesh . i . flat ();
mesh . v = mesh . v . flat ();
mesh . n = mesh . n . flat ();
mesh . t = mesh . t . flat ();
this . numTris = mesh . i . length ; // number of indices
// ------------------
const s = 1.0 ;
this . positions = new Float32Array ( mesh . v );
this . indices = new Uint32Array ( mesh . i );
this . normals = new Float32Array ( mesh . n );
this . uvs = new Float32Array ( mesh . t );
this . timer = new Float32Array ([ 0.0 ]);
this . positionBuffer = device . createBuffer ({
size : this . positions . byteLength ,
usage : GPUBufferUsage . VERTEX | GPUBufferUsage . COPY_DST
});
this . normalBuffer = device . createBuffer ({
size : this . normals . byteLength ,
usage : GPUBufferUsage . VERTEX | GPUBufferUsage . COPY_DST
});
this . uvBuffer = device . createBuffer ({
size : this . uvs . byteLength ,
usage : GPUBufferUsage . VERTEX | GPUBufferUsage . COPY_DST
});
this . indicesBuffer = device . createBuffer ({
size : this . indices . byteLength ,
usage : GPUBufferUsage . INDEX | GPUBufferUsage . COPY_DST
});
this . timerBuffer = device . createBuffer ({
size : 4 , // single float
usage : GPUBufferUsage . UNIFORM | GPUBufferUsage . COPY_DST
});
device . queue . writeBuffer ( this . positionBuffer , 0 , this . positions );
device . queue . writeBuffer ( this . normalBuffer , 0 , this . normals );
device . queue . writeBuffer ( this . indicesBuffer , 0 , this . indices );
device . queue . writeBuffer ( this . uvBuffer , 0 , this . uvs );
device . queue . writeBuffer ( this . timerBuffer , 0 , this . timer );
// var vertWGSL = document.getElementById('vertex.wgsl').innerHTML;
// var fragWGSL = document.getElementById('fragment.wgsl').innerHTML;
var vertWGSL = `
struct Uniforms {
modelMatrix : mat4x4<f32>,
viewMatrix : mat4x4<f32>,
projMatrix : mat4x4<f32>,
};
@binding(0) @group(0) var<uniform> uniforms : Uniforms;
struct VSOut {
@builtin(position) Position : vec4<f32>,
@location(0) normal : vec3<f32>,
@location(1) uvs : vec2<f32>,
@location(2) pos : vec4<f32>,
};
@vertex
fn main(@location(0) inPos : vec3<f32>,
@location(1) normal : vec3<f32>,
@location(2) uvs : vec2<f32>) -> VSOut
{
let mvMatrix = uniforms.viewMatrix * uniforms.modelMatrix;
let mvp = uniforms.projMatrix * mvMatrix;
var vsOut: VSOut;
vsOut.Position = mvp * vec4<f32>( inPos, 1.0);
vsOut.uvs = uvs;
vsOut.normal = ( uniforms.modelMatrix * vec4<f32>(normal, 0.0) ).xyz;
vsOut.pos = ( mvMatrix * vec4<f32>(inPos,1.0) );
return vsOut;
}
`;
var fragWGSL = `
@group(0) @binding(1) var mySampler: sampler;
@group(0) @binding(2) var myTexture: texture_2d<f32>;
@group(0) @binding(3) var <uniform> myTimer: f32;
@fragment
fn main(@location(0) normal : vec3<f32>,
@location(1) uvs : vec2<f32>,
@location(2) position : vec4<f32> ) -> @location(0) vec4<f32>
{
// calculate the 'fog' factor - use the scaled position
// z goes from 0 to 1.0
let pos = position / position.w;
let dist = abs( pos.z * 0.1 );
return vec4<f32>(dist, dist, dist, 1.0);
/*
let scrolluv = uvs + vec2<f32>( myTimer*0.2, 0.0 );
let texCol = textureSample(myTexture, mySampler, scrolluv );
let lightdirection = normalize( vec3<f32>(1, 0, 1) );
let nnormal = normalize( -normal );
// scale brightness based on the normal vs ref drection
let illum = clamp( dot( lightdirection, nnormal ), 0.0, 1.0 ) * 2.0;
return vec4<f32>( texCol.xyz*illum , 1.0 );
*/
}
`;
// ----------------------------------------------------------------
let textureSampler = device . createSampler ({
minFilter : "linear" ,
magFilter : "linear" ,
addressModeU : "repeat" ,
addressModeV : "repeat" ,
addressModeW : "repeat" ,
});
const img = document . createElement ( "img" );
img . src = textureName ; // 'https://webgpulab.xbdev.net/var/images/earth.jpg';
await img . decode ();
const basicTexture = device . createTexture ({
size : [ img . width , img . height , 1 ],
format : "rgba8unorm" , // "bgra8unorm",
usage : GPUTextureUsage . COPY_DST | GPUTextureUsage . TEXTURE_BINDING
});
const imageCanvas = document . createElement ( 'canvas' );
imageCanvas . width = img . width ;
imageCanvas . height = img . height ;
const imageCanvasContext = imageCanvas . getContext ( '2d' );
imageCanvasContext . drawImage ( img , 0 , 0 , imageCanvas . width , imageCanvas . height );
const imageData = imageCanvasContext . getImageData ( 0 , 0 , imageCanvas . width , imageCanvas . height );
const textureData = imageData . data ;
device . queue . writeTexture ( { texture : basicTexture },
textureData ,
{ offset : 0 ,
bytesPerRow : img . width * 4 ,
rowsPerImage : img . height
},
[ img . width , img . height , 1 ] );
// ----------------------------------------------------------------
this . mvpUniformBuffer = device . createBuffer ({
size : 64 * 3 ,
usage : GPUBufferUsage . UNIFORM | GPUBufferUsage . COPY_DST
});
// ----------------------------------------------------------------
this . sceneUniformBindGroupLayout = device . createBindGroupLayout ({
entries : [
{ binding : 0 , visibility : GPUShaderStage . VERTEX , buffer : { type : "uniform" } },
{ binding : 1 , visibility : GPUShaderStage . FRAGMENT , sampler : { type : "filtering" } },
{ binding : 2 , visibility : GPUShaderStage . FRAGMENT , texture : { sampleType : "float" ,
viewDimension : "2d" } },
{ binding : 3 , visibility : GPUShaderStage . FRAGMENT , buffer : { type : "uniform" } },
]
});
this . uniformBindGroup = device . createBindGroup ({
layout : this . sceneUniformBindGroupLayout ,
entries : [
{ binding : 0 , resource : { buffer : this . mvpUniformBuffer } },
{ binding : 1 , resource : textureSampler },
{ binding : 2 , resource : basicTexture . createView () },
{ binding : 3 , resource : { buffer : this . timerBuffer } }
],
});
// ----------------------------------------------------------------
this . pipeline = device . createRenderPipeline ({
layout : device . createPipelineLayout ({ bindGroupLayouts : [ this . sceneUniformBindGroupLayout ]}),
vertex : { module : device . createShaderModule ({
code : vertWGSL }),
entryPoint : 'main' ,
buffers : [ { arrayStride : 12 , attributes : [{ shaderLocation : 0 ,
format : "float32x3" ,
offset : 0 }] },
{ arrayStride : 12 , attributes : [{ shaderLocation : 1 ,
format : "float32x3" ,
offset : 0 }] },
{ arrayStride : 8 , attributes : [{ shaderLocation : 2 ,
format : "float32x2" ,
offset : 0 }] }
]},
fragment : { module : device . createShaderModule ({
code : fragWGSL , }),
entryPoint : 'main' ,
targets : [{ format : presentationFormat }] },
primitive : { topology : 'triangle-list' ,
frontFace : "ccw" ,
cullMode : 'back' ,
stripIndexFormat : undefined },
depthStencil : {
depthWriteEnabled : true ,
depthCompare : 'less' ,
format : 'depth24plus' }
});
this . meshData = { active : false , p :{ x : 0 , y : 0 , z : 0 }, r :{ x : 0 , y : 0 , z : 0 }, s :{ x : 1 , y : 1 , z : 1 } };
} // end create
// -------------------------------
this . getMeshData = function ()
{
return this . meshData ;
}
// ------------------------------
function buildMatrix ( p , r , s )
{
// Create the matrix in Javascript (using matrix library)
const modelMatrix = mat4 . create ();
// create the model transform with a rotation and translation
let translateMat = mat4 . create (); mat4 . fromTranslation ( translateMat , Object . values ( p ) );
let rotateXMat = mat4 . create (); mat4 . fromXRotation ( rotateXMat , r . x );
let rotateYMat = mat4 . create (); mat4 . fromYRotation ( rotateYMat , r . y );
let rotateZMat = mat4 . create (); mat4 . fromZRotation ( rotateZMat , r . z );
let scaleMat = mat4 . create (); mat4 . fromScaling ( scaleMat , Object . values ( s ) );
mat4 . multiply ( modelMatrix , modelMatrix , translateMat );
mat4 . multiply ( modelMatrix , modelMatrix , rotateXMat );
mat4 . multiply ( modelMatrix , modelMatrix , rotateYMat );
mat4 . multiply ( modelMatrix , modelMatrix , rotateZMat );
mat4 . multiply ( modelMatrix , modelMatrix , scaleMat );
return modelMatrix ;
}
this . draw = async function( device , context , depthTexture , viewMatrix , projectionMatrix )
{
// --------------------------------------------------
// Update uniform buffer
const modelMatrix = buildMatrix ( this . meshData . p , this . meshData . r , this . meshData . s );
device . queue . writeBuffer ( this . mvpUniformBuffer , 0 , modelMatrix );
device . queue . writeBuffer ( this . mvpUniformBuffer , 64 , viewMatrix );
device . queue . writeBuffer ( this . mvpUniformBuffer , 128 , projectionMatrix );
this . timer [ 0 ] = this . timer [ 0 ] + 0.01 ;
device . queue . writeBuffer ( this . timerBuffer , 0 , this . timer );
// --------------------------------------------------
// GPURenderPassDescriptor
this . renderPassDescriptor = {
colorAttachments : [{
view : undefined , // asign later in frame
loadOp : "load" , clearValue : { r : 0.0 , g : 0.0 , b : 0.0 , a : 1.0 },
storeOp : 'store' }],
depthStencilAttachment : {
view : depthTexture . createView (),
depthLoadOp : "load" ,
depthClearValue : 1.0 ,
depthStoreOp : 'store' ,
}
};
// -------------------------------------------------
this . renderPassDescriptor . colorAttachments [ 0 ]. view = context . getCurrentTexture (). createView ();
const commandEncoder = device . createCommandEncoder ();
const renderPass = commandEncoder . beginRenderPass ( this . renderPassDescriptor );
renderPass . setPipeline ( this . pipeline );
renderPass . setBindGroup ( 0 , this . uniformBindGroup );
renderPass . setVertexBuffer ( 0 , this . positionBuffer );
renderPass . setVertexBuffer ( 1 , this . normalBuffer );
renderPass . setVertexBuffer ( 2 , this . uvBuffer );
renderPass . setIndexBuffer ( this . indicesBuffer , 'uint32' );
renderPass . drawIndexed ( this . numTris , 1 , 0 , 0 );
renderPass . end ();
await device . queue . submit ([ commandEncoder . finish ()]);
} // end render(..)
} // end sphere
Resources and Links
• WebGPU Lab Example [LINK ]