yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
43d0c2100
master
1// model.cpp 2#include "model.h" 3 4#include "window.h" 5 6#define TINYOBJLOADER_IMPLEMENTATION 7#include "tinyobjloader/tiny_obj_loader.h" 8 9#define STB_IMAGE_IMPLEMENTATION 10#include "stb_image.h" 11 12#define STB_IMAGE_RESIZE_IMPLEMENTATION 13#include "glm/glm/glm.hpp" 14#include "glm/glm/gtc/constants.hpp" 15#include "glm/glm/gtc/matrix_transform.hpp" 16#include "stb_image_resize.h" 17 18#include <memory> 19#include <unordered_map> 20#include <unordered_set> 21 22namespace platform 23{ 24 25using namespace rhi ; 26using namespace Slang ; 27 28// TinyObj provides a tuple type that bundles up indices, but doesn't 29// provide equality comparison or hashing for that type. We'd like 30// to have a hash function so that we can unique indices. 31// 32// In the simplest case, we could define hashing and operator== operations 33// directly on `tinobj::index_t`, but that would create problems if they 34// revise their API. 35// 36// We will instead define our own wrapper type that supports equality 37// comparisons. 38// 39struct ObjIndexKey 40{ 41 tinyobj::index_t index ; 42}; 43 44bool operator== (ObjIndexKey const & left ,ObjIndexKey const & right ) 45{ 46return left .index .vertex_index == right .index .vertex_index && 47left .index .normal_index == right .index .normal_index && 48left .index .texcoord_index == right .index .texcoord_index ; 49} 50 51struct Hasher 52{ 53template < typename T > 54void add (T const & v ) 55 { 56state ^= std::hash < T > ()(v )+ 0x9e3779b9 + (state <<6 )+ (state >>2 ); 57 } 58size_t state = 0 ; 59}; 60 61struct SmoothingGroupVertexID 62{ 63size_t smoothingGroup ; 64size_t positionID ; 65}; 66bool operator== (SmoothingGroupVertexID const & left ,SmoothingGroupVertexID const & right ) 67{ 68return left .smoothingGroup == right .smoothingGroup && left .positionID == right .positionID ; 69} 70 71}// namespace platform 72 73namespace std 74{ 75template <> 76struct hash < platform::ObjIndexKey > 77{ 78size_t operator()(platform::ObjIndexKey const & key )const 79 { 80 platform::Hasher hasher ; 81hasher .add (key .index .vertex_index ); 82hasher .add (key .index .normal_index ); 83hasher .add (key .index .texcoord_index ); 84return hasher .state ; 85 } 86}; 87 88template <> 89struct hash < platform::SmoothingGroupVertexID > 90{ 91size_t operator()(platform::SmoothingGroupVertexID const & id )const 92 { 93 platform::Hasher hasher ; 94hasher .add (id .smoothingGroup ); 95hasher .add (id .positionID ); 96return hasher .state ; 97 } 98}; 99}// namespace std 100 101namespace platform 102{ 103 104ComPtr < ITexture > loadTextureImage (IDevice * device ,char const * path ) 105{ 106int extentX = 0 ; 107int extentY = 0 ; 108int originalChannelCount = 0 ; 109int requestedChannelCount = 4 ;// force to 4-component result 110stbi_uc * data = 111stbi_load (path ,& extentX ,& extentY ,& originalChannelCount ,requestedChannelCount ); 112if (!data ) 113return nullptr ; 114 115int channelCount = requestedChannelCount ?requestedChannelCount :originalChannelCount ; 116 117Format format ; 118switch (channelCount ) 119 { 120default : 121return nullptr ; 122 123case 4 : 124format = Format ::RGBA8Unorm ; 125 126// TODO: handle other cases here if/when we stop forcing 4-component 127// results when loading the image with stb_image. 128 } 129 130 std::vector < SubresourceData > subresourceInitData ; 131 132ptrdiff_t stride = extentX * channelCount * sizeof (stbi_uc ); 133 134SubresourceData baseInitData ; 135baseInitData .data = data ; 136baseInitData .rowPitch = stride ; 137baseInitData .slicePitch = 0 ; 138 139subresourceInitData .push_back (baseInitData ); 140 141// create down-sampled images for the different mip levels 142bool generateMips = true; 143if (generateMips ) 144 { 145int prevExtentX = extentX ; 146int prevExtentY = extentY ; 147stbi_uc * prevData = data ; 148int prevStride = int (stride ); 149 150for (;;) 151 { 152if (prevExtentX == 1 && prevExtentY == 1 ) 153break ; 154 155int newExtentX = prevExtentX /2 ; 156int newExtentY = prevExtentY /2 ; 157 158if (!newExtentX ) 159newExtentX = 1 ; 160if (!newExtentY ) 161newExtentY = 1 ; 162 163stbi_uc * newData = 164 (stbi_uc * )malloc (newExtentX * newExtentY * channelCount * sizeof (stbi_uc )); 165int newStride = int (newExtentX * channelCount * sizeof (stbi_uc )); 166 167stbir_resize_uint8_srgb ( 168prevData , 169prevExtentX , 170prevExtentY , 171prevStride , 172newData , 173newExtentX , 174newExtentY , 175newStride , 176channelCount , 177STBIR_ALPHA_CHANNEL_NONE , 178STBIR_FLAG_ALPHA_PREMULTIPLIED ); 179 180 181SubresourceData mipInitData ; 182mipInitData .data = newData ; 183mipInitData .rowPitch = newStride ; 184mipInitData .slicePitch = 0 ; 185 186subresourceInitData .push_back (mipInitData ); 187 188prevExtentX = newExtentX ; 189prevExtentY = newExtentY ; 190prevData = newData ; 191prevStride = newStride ; 192 } 193 } 194 195int mipCount = (int )subresourceInitData .size (); 196 197TextureDesc desc = {}; 198desc .type = TextureType ::Texture2D ; 199desc .usage = TextureUsage ::ShaderResource ; 200desc .format = format ; 201desc .size .width = extentX ; 202desc .size .height = extentY ; 203desc .size .depth = 1 ; 204desc .mipCount = mipCount ; 205auto texture = device -> createTexture (desc ,subresourceInitData .data ()); 206free (data ); 207 208return texture ; 209} 210 211static std::string makeString (const char * start ,const char * end ) 212{ 213return std::string (start ,size_t (end - start )); 214} 215 216SlangResult ModelLoader ::load (char const * inputPath ,void ** outModel ) 217{ 218// TODO: need to actually allocate/load the data 219 220 tinyobj::attrib_t objVertexAttributes ; 221 std::vector < tinyobj::shape_t > objShapes ; 222 std::vector < tinyobj::material_t > objMaterials ; 223 224 std::string baseDir ; 225if (auto lastSlash = strrchr (inputPath ,'/' )) 226 { 227baseDir = makeString (inputPath ,lastSlash ); 228 } 229 230 std::string diagnostics ; 231bool shouldTriangulate = true; 232bool success = tinyobj::LoadObj ( 233& objVertexAttributes , 234& objShapes , 235& objMaterials , 236& diagnostics , 237inputPath , 238baseDir .size () ?baseDir .c_str () :nullptr , 239shouldTriangulate ); 240 241if (!diagnostics .empty ()) 242 { 243printf ("%s" ,diagnostics .c_str ()); 244 } 245if (!success ) 246 { 247return SLANG_FAIL ; 248 } 249 250// Translate each material imported by TinyObj into a format that 251// we can actually use for rendering. 252// 253 std::vector < void *> materials ; 254for (auto & objMaterial :objMaterials ) 255 { 256MaterialData materialData ; 257 258materialData .diffuseColor = 259 glm::vec3 (objMaterial .diffuse [0 ],objMaterial .diffuse [1 ],objMaterial .diffuse [2 ]); 260 261materialData .specularColor = 262 glm::vec3 (objMaterial .specular [0 ],objMaterial .specular [1 ],objMaterial .specular [2 ]); 263 264materialData .specularity = objMaterial .shininess ; 265 266// load any referenced textures here 267if (objMaterial .diffuse_texname .length ()) 268 { 269materialData .diffuseMap = loadTextureImage (device ,objMaterial .diffuse_texname .c_str ()); 270 } 271 272auto material = callbacks -> createMaterial (materialData ); 273materials .push_back (material ); 274 } 275 276// Flip the winding order on all faces if we are asked to... 277// 278if (loadFlags & LoadFlag ::FlipWinding ) 279 { 280for (auto & objShape :objShapes ) 281 { 282size_t objIndexCounter = 0 ; 283size_t objFaceCounter = 0 ; 284for (auto objFaceVertexCount :objShape .mesh .num_face_vertices ) 285 { 286size_t beginIndex = objIndexCounter ; 287size_t endIndex = beginIndex + objFaceVertexCount ; 288objIndexCounter = endIndex ; 289 290size_t halfCount = objFaceVertexCount /2 ; 291for (size_t ii = 0 ;ii < halfCount ;++ ii ) 292 { 293 std::swap ( 294objShape .mesh .indices [beginIndex + ii ], 295objShape .mesh .indices [endIndex - (ii + 1 )]); 296 } 297 } 298 } 299 } 300 301// Identify cases where a face has a vertex without a normal, and in that 302// case remember that the given vertex needs to be "smoothed" as part of 303// the smoothing group for that face. Note that it is possible for the 304// same vertex (position) to be part of faces in distinct smoothing groups. 305// 306 std::unordered_map < SmoothingGroupVertexID ,size_t > smoothedVertexNormals ; 307size_t firstSmoothedNormalID = objVertexAttributes .normals .size () /3 ; 308size_t flatFaceCounter = 0 ; 309for (auto & objShape :objShapes ) 310 { 311size_t objIndexCounter = 0 ; 312size_t objFaceCounter = 0 ; 313for (auto objFaceVertexCount :objShape .mesh .num_face_vertices ) 314 { 315const size_t flatFaceIndex = flatFaceCounter ++ ; 316const size_t objFaceIndex = objFaceCounter ++ ; 317size_t smoothingGroup = objShape .mesh .smoothing_group_ids [objFaceIndex ]; 318if (!smoothingGroup ) 319 { 320smoothingGroup = ~flatFaceIndex ; 321 } 322 323for (size_t objFaceVertex = 0 ;objFaceVertex < objFaceVertexCount ;++ objFaceVertex ) 324 { 325 tinyobj::index_t & objIndex = objShape .mesh .indices [objIndexCounter ++ ]; 326 327if (objIndex .normal_index < 0 ) 328 { 329SmoothingGroupVertexID smoothVertexID ; 330smoothVertexID .positionID = objIndex .vertex_index ; 331smoothVertexID .smoothingGroup = smoothingGroup ; 332 333if (smoothedVertexNormals .find (smoothVertexID )== smoothedVertexNormals .end ()) 334 { 335size_t normalID = objVertexAttributes .normals .size () /3 ; 336objVertexAttributes .normals .push_back (0 ); 337objVertexAttributes .normals .push_back (0 ); 338objVertexAttributes .normals .push_back (0 ); 339 340smoothedVertexNormals .insert (std::make_pair (smoothVertexID ,normalID )); 341 342objIndex .normal_index = int (normalID ); 343 } 344 } 345 } 346 } 347 } 348// 349// Having identified which vertices we need to smooth, we will make another 350// pass to compute face normals and apply them to the vertices that belong 351// to the same smoothing group. 352// 353flatFaceCounter = 0 ; 354for (auto & objShape :objShapes ) 355 { 356size_t objIndexCounter = 0 ; 357size_t objFaceCounter = 0 ; 358for (auto objFaceVertexCount :objShape .mesh .num_face_vertices ) 359 { 360const size_t flatFaceIndex = flatFaceCounter ++ ; 361const size_t objFaceIndex = objFaceCounter ++ ; 362size_t smoothingGroup = objShape .mesh .smoothing_group_ids [objFaceIndex ]; 363if (!smoothingGroup ) 364 { 365smoothingGroup = ~flatFaceIndex ; 366 } 367 368 glm::vec3 faceNormal ; 369if (objFaceVertexCount >=3 ) 370 { 371 glm::vec3 v [3 ]; 372for (size_t objFaceVertex = 0 ;objFaceVertex < 3 ;++ objFaceVertex ) 373 { 374 tinyobj::index_t objIndex = 375objShape .mesh .indices [objIndexCounter + objFaceVertex ]; 376if (objIndex .vertex_index >=0 ) 377 { 378v [objFaceVertex ]= glm::vec3 ( 379objVertexAttributes .vertices [3 * objIndex .vertex_index + 0 ], 380objVertexAttributes .vertices [3 * objIndex .vertex_index + 1 ], 381objVertexAttributes .vertices [3 * objIndex .vertex_index + 2 ]); 382 } 383 } 384faceNormal = cross (v [1 ]- v [0 ],v [2 ]- v [0 ]); 385 } 386 387// Add this face normal to any to-be-smoothed vertex on the face. 388for (size_t objFaceVertex = 0 ;objFaceVertex < objFaceVertexCount ;++ objFaceVertex ) 389 { 390 tinyobj::index_t objIndex = objShape .mesh .indices [objIndexCounter ++ ]; 391 392SmoothingGroupVertexID smoothVertexID ; 393smoothVertexID .positionID = objIndex .vertex_index ; 394smoothVertexID .smoothingGroup = smoothingGroup ; 395 396auto ii = smoothedVertexNormals .find (smoothVertexID ); 397if (ii != smoothedVertexNormals .end ()) 398 { 399size_t normalID = ii -> second ; 400objVertexAttributes .normals [normalID * 3 + 0 ]+= faceNormal .x ; 401objVertexAttributes .normals [normalID * 3 + 1 ]+= faceNormal .y ; 402objVertexAttributes .normals [normalID * 3 + 2 ]+= faceNormal .z ; 403 } 404 } 405 } 406 } 407// 408// Once we've added all contributions from each smoothing group, 409// we can normalize the normals to compute the area-weighted average. 410// 411size_t normalCount = objVertexAttributes .normals .size () /3 ; 412for (size_t ii = firstSmoothedNormalID ;ii < normalCount ;++ ii ) 413 { 414 glm::vec3 normal = glm::vec3 ( 415objVertexAttributes .normals [3 * ii + 0 ], 416objVertexAttributes .normals [3 * ii + 1 ], 417objVertexAttributes .normals [3 * ii + 2 ]); 418 419normal = normalize (normal ); 420 421objVertexAttributes .normals [3 * ii + 0 ]= normal .x ; 422objVertexAttributes .normals [3 * ii + 1 ]= normal .y ; 423objVertexAttributes .normals [3 * ii + 2 ]= normal .z ; 424 } 425 426// TODO: we should sort the faces to group faces with 427// the same material ID together, in case they weren't 428// grouped in the original file. 429 430// We need to undo the .obj indexing stuff so that we have 431// standard position/normal/etc. data in a single flat array 432 433 std::unordered_map < ObjIndexKey ,Index > mapObjIndexToFlatIndex ; 434 std::vector < Vertex > flatVertices ; 435 std::vector < Index > flatIndices ; 436 437MeshData * currentMesh = nullptr ; 438MeshData currentMeshStorage ; 439 440 std::vector < void *> meshes ; 441 442void * defaultMaterial = nullptr ; 443 444for (auto & objShape :objShapes ) 445 { 446size_t objIndexCounter = 0 ; 447size_t objFaceCounter = 0 ; 448for (auto objFaceVertexCount :objShape .mesh .num_face_vertices ) 449 { 450size_t objFaceIndex = objFaceCounter ++ ; 451int faceMaterialID = objShape .mesh .material_ids [objFaceIndex ]; 452void * faceMaterial = nullptr ; 453if (faceMaterialID < 0 ) 454 { 455if (!defaultMaterial ) 456 { 457MaterialData defaultMaterialData ; 458defaultMaterialData .diffuseColor = glm::vec3 (0.5 ,0.5 ,0.5 ); 459defaultMaterial = callbacks -> createMaterial (defaultMaterialData ); 460 } 461faceMaterial = defaultMaterial ; 462 } 463else 464 { 465faceMaterial = materials [faceMaterialID ]; 466 } 467 468if (!currentMesh || (faceMaterial != currentMesh -> material )) 469 { 470// finish old mesh. 471if (currentMesh ) 472 { 473meshes .push_back (callbacks -> createMesh (* currentMesh )); 474 } 475 476// Need to start a new mesh. 477currentMesh = & currentMeshStorage ; 478currentMesh -> material = faceMaterial ; 479currentMesh -> firstIndex = (int )flatIndices .size (); 480currentMesh -> indexCount = 0 ; 481 } 482 483for (size_t objFaceVertex = 0 ;objFaceVertex < objFaceVertexCount ;++ objFaceVertex ) 484 { 485 tinyobj::index_t objIndex = objShape .mesh .indices [objIndexCounter ++ ]; 486ObjIndexKey objIndexKey ; 487objIndexKey .index = objIndex ; 488 489 490Index flatIndex = Index (-1 ); 491auto iter = mapObjIndexToFlatIndex .find (objIndexKey ); 492if (iter != mapObjIndexToFlatIndex .end ()) 493 { 494flatIndex = iter -> second ; 495 } 496else 497 { 498Vertex flatVertex ; 499if (objIndex .vertex_index >=0 ) 500 { 501flatVertex .position = 502scale * 503 glm::vec3 ( 504objVertexAttributes .vertices [3 * objIndex .vertex_index + 0 ], 505objVertexAttributes .vertices [3 * objIndex .vertex_index + 1 ], 506objVertexAttributes .vertices [3 * objIndex .vertex_index + 2 ]); 507 } 508if (objIndex .normal_index >=0 ) 509 { 510flatVertex .normal = glm::vec3 ( 511objVertexAttributes .normals [3 * objIndex .normal_index + 0 ], 512objVertexAttributes .normals [3 * objIndex .normal_index + 1 ], 513objVertexAttributes .normals [3 * objIndex .normal_index + 2 ]); 514 } 515if (objIndex .texcoord_index >=0 ) 516 { 517flatVertex .uv = glm::vec2 ( 518objVertexAttributes .texcoords [2 * objIndex .texcoord_index + 0 ], 519objVertexAttributes .texcoords [2 * objIndex .texcoord_index + 1 ]); 520 } 521 522flatIndex = uint32_t (flatVertices .size ()); 523mapObjIndexToFlatIndex .insert (std::make_pair (objIndexKey ,flatIndex )); 524flatVertices .push_back (flatVertex ); 525 } 526 527flatIndices .push_back (flatIndex ); 528currentMesh -> indexCount ++ ; 529 } 530 } 531 } 532 533// finish last mesh. 534if (currentMesh ) 535 { 536meshes .push_back (callbacks -> createMesh (* currentMesh )); 537 } 538 539ModelData modelData ; 540 541modelData .vertexCount = (int )flatVertices .size (); 542modelData .indexCount = (int )flatIndices .size (); 543modelData .primitiveTopology = PrimitiveTopology ::TriangleList ; 544 545modelData .meshCount = int (meshes .size ()); 546modelData .meshes = meshes .data (); 547 548BufferDesc vertexBufferDesc ; 549vertexBufferDesc .size = modelData .vertexCount * sizeof (Vertex ); 550vertexBufferDesc .usage = BufferUsage ::VertexBuffer ; 551vertexBufferDesc .elementSize = sizeof (Vertex ); 552 553modelData .vertexBuffer = device -> createBuffer (vertexBufferDesc ,flatVertices .data ()); 554if (!modelData .vertexBuffer ) 555return SLANG_FAIL ; 556 557BufferDesc indexBufferDesc ; 558indexBufferDesc .size = modelData .indexCount * sizeof (Index ); 559indexBufferDesc .usage = BufferUsage ::IndexBuffer ; 560indexBufferDesc .elementSize = sizeof (Index ); 561 562modelData .indexBuffer = device -> createBuffer (indexBufferDesc ,flatIndices .data ()); 563if (!modelData .indexBuffer ) 564return SLANG_FAIL ; 565 566* outModel = callbacks -> createModel (modelData ); 567 568return SLANG_OK ; 569} 570 571}// namespace platform