diff --git a/simulator/model.cpp b/simulator/model.cpp index 0c27d4fec..bc437b050 100644 --- a/simulator/model.cpp +++ b/simulator/model.cpp @@ -18,8 +18,9 @@ namespace mrover { Assimp::Importer importer; importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_POINT | aiPrimitiveType_LINE); // Drop points and lines - // aiScene const* scene = importer.ReadFile(uri.data(),aiProcessPreset_TargetRealtime_MaxQuality); - aiScene const* scene = importer.ReadFile(uriToPath(uri), aiProcessPreset_TargetRealtime_Fast | aiProcess_FlipUVs); + // we switch to GenSmoothNormals because this seems to fix normals on some meshes + // if you shade flat in blender it shouldn't smooth it + aiScene const* scene = importer.ReadFile(uriToPath(uri), (aiProcessPreset_TargetRealtime_Fast ^ aiProcess_GenNormals) | aiProcess_GenSmoothNormals | aiProcess_FlipUVs); if (!scene) throw std::runtime_error{std::format("Scene import error: {} on path: {}", importer.GetErrorString(), uri)}; RCLCPP_INFO_STREAM(logger, std::format("Loaded scene: {} with mesh count: {}", uri, scene->mNumMeshes)); @@ -34,7 +35,7 @@ namespace mrover { if (!mesh->HasNormals()) throw std::invalid_argument{std::format("Mesh #{} has no normals", meshIndex)}; if (!mesh->HasTextureCoords(0)) throw std::invalid_argument{std::format("Mesh #{} has no texture coordinates", meshIndex)}; - auto& [vertices, normals, uvs, indices, texture] = meshes.emplace_back(); + auto& [vertices, normals, tangents, bitangents, uvs, indices, texture, normal_map, roughness, metallic] = meshes.emplace_back(); assert(mesh->HasPositions()); vertices.data.resize(mesh->mNumVertices); @@ -67,6 +68,17 @@ namespace mrover { uvs.data[uvIndex] = {uv.x, uv.y}; } + assert(mesh->HasTangentsAndBitangents()); + tangents.data.resize(mesh->mNumVertices); + bitangents.data.resize(mesh->mNumVertices); + for (std::size_t vertexIndex = 0; vertexIndex < mesh->mNumVertices; ++vertexIndex) { + aiVector3D const& tangent = mesh->mTangents[vertexIndex]; + aiVector3D const& bitangent = mesh->mBitangents[vertexIndex]; + tangents.data[vertexIndex] = Eigen::Vector3f{tangent.x, tangent.y, tangent.z}; + // from testing it seems like the bitangents blender exports are backwards, but this seems sus + bitangents.data[vertexIndex] = Eigen::Vector3f{-bitangent.x, -bitangent.y, -bitangent.z}; + } + if (aiMaterial const* material = scene->mMaterials[mesh->mMaterialIndex]) { if (aiString path; material->GetTextureCount(aiTextureType_DIFFUSE) > 0 && material->GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS) { texture.data = readTexture(path.C_Str()); @@ -82,6 +94,23 @@ namespace mrover { } aiString name; material->Get(AI_MATKEY_NAME, name); + + if (aiString path; material->GetTextureCount(aiTextureType_NORMALS) > 0 && material->GetTexture(aiTextureType_NORMALS, 0, &path) == AI_SUCCESS) { + normal_map.data = readTexture(path.C_Str()); + } else { + // create a 1x1 texture with normal pointing straight out + // this is simply the vector (0, 0, 1) (points up in the Z direction) + cv::Scalar color(255, 128, 128); + normal_map.data = cv::Mat{1, 1, CV_8UC4, color}; + } + + material->Get(AI_MATKEY_ROUGHNESS_FACTOR, roughness); + RCLCPP_INFO_STREAM(logger, std::format("\t\troughness: {}", roughness)); + + // don't ask me why metallic is this and not AI_MATKEY_METALLIC_FACTOR + material->Get(AI_MATKEY_REFLECTIVITY, metallic); + RCLCPP_INFO_STREAM(logger, std::format("\t\tmetallic: {}", metallic)); + RCLCPP_INFO_STREAM(logger, std::format("\tLoaded material: {}", name.C_Str())); } diff --git a/simulator/shaders/shaders.wgsl b/simulator/shaders/shaders.wgsl index d751dcc23..5b83449ec 100644 --- a/simulator/shaders/shaders.wgsl +++ b/simulator/shaders/shaders.wgsl @@ -14,21 +14,28 @@ struct SceneUniforms { struct MeshUniforms { modelToWorld: mat4x4f, modelToWorldForNormals: mat4x4f, + roughness: f32, + metallic: f32, } @group(0) @binding(0) var mu: MeshUniforms; @group(0) @binding(1) var texture: texture_2d; @group(0) @binding(2) var textureSampler: sampler; +@group(0) @binding(3) var normalTexture: texture_2d; struct InVertex { @location(0) positionInModel: vec3f, @location(1) normalInModel: vec3f, - @location(2) uv: vec2f, + @location(2) tangentInModel: vec3f, + @location(3) bitangentInModel: vec3f, + @location(4) uv: vec2f, } struct OutVertex { @builtin(position) positionInClip: vec4f, @location(0) positionInWorld: vec4f, @location(1) normalInWorld: vec4f, - @location(2) uv: vec2f, + @location(2) tangentInWorld: vec4f, + @location(3) bitangentInWorld: vec4f, + @location(4) uv: vec2f, } @vertex fn vs_main(in: InVertex) -> OutVertex { let positionInWorld = mu.modelToWorld * vec4(in.positionInModel, 1); @@ -36,6 +43,9 @@ struct OutVertex { out.positionInClip = su.cameraToClip * su.worldToCamera * positionInWorld; out.positionInWorld = positionInWorld; out.normalInWorld = mu.modelToWorldForNormals * vec4(in.normalInModel, 0); + // TODO: should I just use the regular modelToWorld matrix here? + out.tangentInWorld = mu.modelToWorldForNormals * vec4(in.tangentInModel, 0); + out.bitangentInWorld = mu.modelToWorldForNormals * vec4(in.bitangentInModel, 0); out.uv = in.uv; return out; } @@ -44,27 +54,151 @@ struct OutFragment { @location(0) color: vec4f, @location(1) normalInCamera: vec4f, } +// Blinn-Phong shader @fragment fn fs_main(in: OutVertex) -> OutFragment { let baseColor = textureSample(texture, textureSampler, in.uv); + let normalMapStrength = 1.0; + // each component is in the range [0,1] + let encodedNormal = textureSample(normalTexture, textureSampler, in.uv).rgb; + // shift to get negative values, then normalize because we only care about direction + // this normal is in a frame local to the surface of this face + let normalInLocal = normalize(encodedNormal - 0.5); + + let localToWorld = mat3x3f( + normalize(in.tangentInWorld).xyz, + normalize(in.bitangentInWorld).xyz, + normalize(in.normalInWorld).xyz, + ); + + let normalInWorld = normalize(localToWorld * normalInLocal); + let mixedNormal = normalize(mix(in.normalInWorld, vec4(normalInWorld, 0), normalMapStrength)); + var out : OutFragment; - out.normalInCamera = (su.worldToCamera * in.normalInWorld + vec4(1, 1, 1, 0)) / 2; + out.normalInCamera = (su.worldToCamera * mixedNormal + vec4(1, 1, 1, 0)) / 2; out.normalInCamera.a = 1; // Ambient - let ambientStrength = 0.6; + let ambientStrength = 0.4; let ambient = ambientStrength * su.lightColor; // Diffuse let lightDirInWorld = normalize(su.lightInWorld - in.positionInWorld); - let diff = max(dot(in.normalInWorld, lightDirInWorld), 0.0); + let diff = max(dot(mixedNormal, lightDirInWorld), 0.0); let diffuse = diff * su.lightColor; // Specular - let specularStrength = 0.5; - let viewDirInWolrd = normalize(su.cameraInWorld - in.positionInWorld); - let reflectDir = reflect(-lightDirInWorld, in.normalInWorld); - let spec = pow(max(dot(viewDirInWolrd, reflectDir), 0.0), 32); + let specularStrength = 0.08; + let viewDirInWorld = normalize(su.cameraInWorld - in.positionInWorld); + // blinn-phong + let halfwayDir = normalize(lightDirInWorld + viewDirInWorld); + let spec = pow(max(dot(mixedNormal, halfwayDir), 0.0), 32); + let specular = specularStrength * spec * su.lightColor; // Combination - out.color = vec4(((ambient + diffuse + specular) * baseColor).rgb, 1); + out.color = vec4(((ambient + diffuse) * baseColor + specular).rgb, 1); + return out; +} + +// PBR FS +// see https://learnopengl.com/PBR/Lighting +fn fresnelSchlick(cosTheta: f32, F0: vec3f) -> vec3f { + return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0); +} + +fn DistributionGGX(normal: vec3f, halfway: vec3f, roughness: f32) -> f32 { + let a = roughness * roughness; + let a2 = a * a; + let NdotH = max(dot(normal, halfway), 0.0); + let NdotH2 = NdotH * NdotH; + + let num = a2; + let x = (NdotH2 * (a2 - 1.0) + 1.0); + let denom = 3.14159 * x * x; + + return num / denom; +} + +fn GeometrySchlickGGX(NdotV: f32, roughness: f32) -> f32 { + let r = roughness + 1.0; + let k = r * r / 8.0; + + let num = NdotV; + let denom = NdotV * (1.0 - k) + k; + + return num / denom; +} + +fn GeometrySmith(normal: vec3f, viewDir: vec3f, lightDir: vec3f, roughness: f32) -> f32 { + let NdotV = max(dot(normal, viewDir), 0.0); + let NdotL = max(dot(normal, lightDir), 0.0); + let ggx1 = GeometrySchlickGGX(NdotL, roughness); + let ggx2 = GeometrySchlickGGX(NdotV, roughness); + + return ggx1 * ggx2; +} + +@fragment fn fs_main_pbr(in: OutVertex) -> OutFragment { + // let metallic = 0.0; + let metallic = mu.metallic; + // let roughness = 0.93; + let roughness = mu.roughness; + // crank up the ambient lighting effect otherwise everything is really dark + let ao = 0.8; + + // get the normal vector + let normalMapStrength = 1.0; + // each component is in the range [0,1] + let encodedNormal = textureSample(normalTexture, textureSampler, in.uv).rgb; + // shift to get negative values, then normalize because we only care about direction + // this normal is in a frame local to the surface of this face + let normalInLocal = normalize(encodedNormal - 0.5); + + let localToWorld = mat3x3f( + normalize(in.tangentInWorld).xyz, + normalize(in.bitangentInWorld).xyz, + normalize(in.normalInWorld).xyz, + ); + + let normalInWorld = normalize(localToWorld * normalInLocal); + let mixedNormal = normalize(mix(in.normalInWorld.xyz, normalInWorld, normalMapStrength)); + + let viewDirInWorld = normalize((su.cameraInWorld - in.positionInWorld).xyz); + let lightDirInWorld = normalize((su.lightInWorld - in.positionInWorld).xyz); + let halfwayDir = normalize((lightDirInWorld + viewDirInWorld).xyz); + + // now PBR stuff + let distanceToLight = length(su.lightInWorld - in.positionInWorld); + // let attenuation = 1 / (distanceToLight * distanceToLight); + let attenuation = 1.0; + let radiance = su.lightColor.rgb * attenuation * 4; + + let albedo = textureSample(texture, textureSampler, in.uv).rgb; + let F0 = mix(vec3(0.04), albedo, metallic); + let F = fresnelSchlick(max(dot(halfwayDir, viewDirInWorld), 0), F0); + + let NDF = DistributionGGX(mixedNormal, halfwayDir, roughness); + let G = GeometrySmith(mixedNormal, viewDirInWorld, lightDirInWorld, roughness); + let numerator = NDF * G * F; + let denominator = 4.0 * max(dot(mixedNormal, viewDirInWorld), 0.0) * max(dot(mixedNormal, lightDirInWorld), 0.0) + 0.0001; + let specular = numerator / denominator; + + let kS = F; + let kD = (vec3(1.0) - kS) * (1.0 - metallic); + + let PI = 3.14159265; + let NdotL = max(dot(mixedNormal, lightDirInWorld), 0.0); + let Lo = (kD * albedo / PI + specular) * radiance * NdotL; + let ambient = 0.4 * albedo * ao; + + var color = ambient + Lo; + // gamma correct + // TODO: why does this make it look bad... + // color = color / (color + vec3(1.0)); + // color = pow(color, vec3(1.0/2.2)); + + var out : OutFragment; + out.normalInCamera = (su.worldToCamera * vec4(mixedNormal, 0) + vec4(1, 1, 1, 0)) / 2; + out.normalInCamera.a = 1; + // TODO: think about alpha?? + out.color = vec4(color, 1.0); return out; } diff --git a/simulator/simulator.gui.cpp b/simulator/simulator.gui.cpp index d3fbbde52..044f06a8c 100644 --- a/simulator/simulator.gui.cpp +++ b/simulator/simulator.gui.cpp @@ -68,6 +68,8 @@ namespace mrover { ImGui::Checkbox("Enable Physics (P)", &mEnablePhysics); ImGui::Checkbox("Render Models (M)", &mRenderModels); ImGui::Checkbox("Render Wireframe Colliders (C)", &mRenderWireframeColliders); + ImGui::Checkbox("Use PBR Rendering", &mPbrEnabled); + ImGui::Checkbox("Use Normal Mapping", &mNormalMapEnabled); ImGui::Text("Camera Locked: %s", mCameraInRoverTarget ? "True" : "False"); ImGui::SliderFloat("Camera Lock Lerp", &mCameraLockSlerp, 0.0f, 1.0f); diff --git a/simulator/simulator.hpp b/simulator/simulator.hpp index 32bb0050a..ca1c5b06b 100644 --- a/simulator/simulator.hpp +++ b/simulator/simulator.hpp @@ -37,6 +37,8 @@ namespace mrover { struct ModelUniforms { Eigen::Matrix4f modelToWorld{}; Eigen::Matrix4f modelToWorldForNormals{}; + float roughness; + float metallic; EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; @@ -63,9 +65,15 @@ namespace mrover { struct Mesh { SharedBuffer vertices; SharedBuffer normals; + SharedBuffer tangents; + SharedBuffer bitangents; SharedBuffer uvs; SharedBuffer indices; MeshTexture texture; + MeshTexture normal_map; + // could make these also textures, but that might be doing too much + float roughness; + float metallic; }; // DO NOT access the mesh unless you are certain it has been set from the async loader @@ -242,6 +250,8 @@ namespace mrover { bool mEnablePhysics{}; bool mRenderModels = true; bool mRenderWireframeColliders = false; + bool mPbrEnabled = false; + bool mNormalMapEnabled = false; double mPublishHammerDistanceThreshold = 3; double mPublishBottleDistanceThreshold = 3; float mCameraLockSlerp = 0.02; @@ -313,6 +323,7 @@ namespace mrover { wgpu::TextureView mNormalTextureView; wgpu::ShaderModule mShaderModule; + wgpu::RenderPipeline mBlinnPhongPipeline; wgpu::RenderPipeline mPbrPipeline; wgpu::RenderPipeline mWireframePipeline; @@ -327,6 +338,9 @@ namespace mrover { Eigen::Vector4f mSkyColor{0.05f, 0.8f, 0.92f, 1.0f}; + // 1x1 normal map texture that's just flat + MeshTexture mDefaultNormalMap = {.data = cv::Mat{1, 1, CV_8UC4, {255, 128, 128}}}; + // Physics std::unique_ptr mCollisionConfig; diff --git a/simulator/simulator.render.cpp b/simulator/simulator.render.cpp index 82acd4709..047f2b7e1 100644 --- a/simulator/simulator.render.cpp +++ b/simulator/simulator.render.cpp @@ -70,14 +70,18 @@ namespace mrover { descriptor.depthStencil = &depthStencil; - std::array attributes{}; + std::array attributes{}; attributes[0].format = wgpu::VertexFormat::Float32x3; attributes[0].shaderLocation = 0; attributes[1].format = wgpu::VertexFormat::Float32x3; attributes[1].shaderLocation = 1; - attributes[2].format = wgpu::VertexFormat::Float32x2; + attributes[2].format = wgpu::VertexFormat::Float32x3; attributes[2].shaderLocation = 2; - std::array vertexBufferLayout{}; + attributes[3].format = wgpu::VertexFormat::Float32x3; + attributes[3].shaderLocation = 3; + attributes[4].format = wgpu::VertexFormat::Float32x2; + attributes[4].shaderLocation = 4; + std::array vertexBufferLayout{}; vertexBufferLayout[0].arrayStride = sizeof(float) * 3; vertexBufferLayout[0].stepMode = wgpu::VertexStepMode::Vertex; vertexBufferLayout[0].attributeCount = 1; @@ -86,10 +90,18 @@ namespace mrover { vertexBufferLayout[1].stepMode = wgpu::VertexStepMode::Vertex; vertexBufferLayout[1].attributeCount = 1; vertexBufferLayout[1].attributes = attributes.data() + 1; - vertexBufferLayout[2].arrayStride = sizeof(float) * 2; + vertexBufferLayout[2].arrayStride = sizeof(float) * 3; vertexBufferLayout[2].stepMode = wgpu::VertexStepMode::Vertex; vertexBufferLayout[2].attributeCount = 1; vertexBufferLayout[2].attributes = attributes.data() + 2; + vertexBufferLayout[3].arrayStride = sizeof(float) * 3; + vertexBufferLayout[3].stepMode = wgpu::VertexStepMode::Vertex; + vertexBufferLayout[3].attributeCount = 1; + vertexBufferLayout[3].attributes = attributes.data() + 3; + vertexBufferLayout[4].arrayStride = sizeof(float) * 2; + vertexBufferLayout[4].stepMode = wgpu::VertexStepMode::Vertex; + vertexBufferLayout[4].attributeCount = 1; + vertexBufferLayout[4].attributes = attributes.data() + 4; descriptor.vertex.entryPoint = "vs_main"; descriptor.vertex.module = mShaderModule; @@ -130,7 +142,7 @@ namespace mrover { fragment.targets = targets.data(); descriptor.fragment = &fragment; - std::array meshBindGroupLayoutEntries{}; + std::array meshBindGroupLayoutEntries{}; meshBindGroupLayoutEntries[0].binding = 0; meshBindGroupLayoutEntries[0].visibility = wgpu::ShaderStage::Fragment | wgpu::ShaderStage::Vertex; meshBindGroupLayoutEntries[0].buffer.type = wgpu::BufferBindingType::Uniform; @@ -142,6 +154,10 @@ namespace mrover { meshBindGroupLayoutEntries[2].binding = 2; meshBindGroupLayoutEntries[2].visibility = wgpu::ShaderStage::Fragment; meshBindGroupLayoutEntries[2].sampler.type = wgpu::SamplerBindingType::Filtering; + meshBindGroupLayoutEntries[3].binding = 3; + meshBindGroupLayoutEntries[3].visibility = wgpu::ShaderStage::Fragment; + meshBindGroupLayoutEntries[3].texture.sampleType = wgpu::TextureSampleType::Float; + meshBindGroupLayoutEntries[3].texture.viewDimension = wgpu::TextureViewDimension::_2D; wgpu::BindGroupLayoutDescriptor meshBindGroupLayourDescriptor; meshBindGroupLayourDescriptor.entryCount = meshBindGroupLayoutEntries.size(); @@ -166,8 +182,13 @@ namespace mrover { pipelineLayoutDescriptor.bindGroupLayouts = reinterpret_cast(bindGroupLayouts.data()); descriptor.layout = mDevice.createPipelineLayout(pipelineLayoutDescriptor); + mBlinnPhongPipeline = mDevice.createRenderPipeline(descriptor); + if (!mBlinnPhongPipeline) throw std::runtime_error("Failed to create WGPU Blinn-Phong pipeline"); + + fragment.entryPoint = "fs_main_pbr"; + mPbrPipeline = mDevice.createRenderPipeline(descriptor); - if (!mPbrPipeline) throw std::runtime_error("Failed to create WGPU render pipeline"); + if (!mPbrPipeline) throw std::runtime_error("Failed to create WGPU PBR pipeline"); // TODO(quintin): This is technically not correct. As far as I can tell getting actual wireframe rendering is pretty difficult in WGPU descriptor.primitive.topology = wgpu::PrimitiveTopology::LineList; @@ -295,7 +316,7 @@ namespace mrover { mAdapter.getLimits(&limits); wgpu::RequiredLimits requiredLimits = wgpu::Default; - requiredLimits.limits.maxVertexAttributes = 4; + requiredLimits.limits.maxVertexAttributes = 5; requiredLimits.limits.maxVertexBuffers = 8; requiredLimits.limits.maxBindGroups = 2; requiredLimits.limits.maxUniformBuffersPerShaderStage = 4; @@ -419,10 +440,18 @@ namespace mrover { mesh.indices.enqueueWriteIfUnitialized(mDevice, mQueue, wgpu::BufferUsage::Index); mesh.vertices.enqueueWriteIfUnitialized(mDevice, mQueue, wgpu::BufferUsage::Vertex); mesh.normals.enqueueWriteIfUnitialized(mDevice, mQueue, wgpu::BufferUsage::Vertex); + mesh.tangents.enqueueWriteIfUnitialized(mDevice, mQueue, wgpu::BufferUsage::Vertex); + mesh.bitangents.enqueueWriteIfUnitialized(mDevice, mQueue, wgpu::BufferUsage::Vertex); mesh.uvs.enqueueWriteIfUnitialized(mDevice, mQueue, wgpu::BufferUsage::Vertex); mesh.texture.enqueWriteIfUnitialized(mDevice); - - std::array bindGroupEntires{}; + if (mNormalMapEnabled) + mesh.normal_map.enqueWriteIfUnitialized(mDevice); + else + mDefaultNormalMap.enqueWriteIfUnitialized(mDevice); + uniforms.value.roughness = mesh.roughness; + uniforms.value.metallic = mesh.metallic; + + std::array bindGroupEntires{}; bindGroupEntires[0].binding = 0; bindGroupEntires[0].buffer = uniforms.buffer; bindGroupEntires[0].size = sizeof(ModelUniforms); @@ -430,7 +459,10 @@ namespace mrover { bindGroupEntires[1].textureView = mesh.texture.view; bindGroupEntires[2].binding = 2; bindGroupEntires[2].sampler = mesh.texture.sampler; + bindGroupEntires[3].binding = 3; + bindGroupEntires[3].textureView = mNormalMapEnabled ? mesh.normal_map.view : mDefaultNormalMap.view; wgpu::BindGroupDescriptor descriptor; + // we can just use mPbrPipeline here because both PBR and Blinn-Phone use the same bind groups descriptor.layout = mPbrPipeline.getBindGroupLayout(0); descriptor.entryCount = bindGroupEntires.size(); descriptor.entries = bindGroupEntires.data(); @@ -439,7 +471,9 @@ namespace mrover { pass.setBindGroup(0, bindGroup, 0, nullptr); pass.setVertexBuffer(0, mesh.vertices.buffer, 0, mesh.vertices.sizeBytes()); pass.setVertexBuffer(1, mesh.normals.buffer, 0, mesh.normals.sizeBytes()); - pass.setVertexBuffer(2, mesh.uvs.buffer, 0, mesh.uvs.sizeBytes()); + pass.setVertexBuffer(2, mesh.tangents.buffer, 0, mesh.tangents.sizeBytes()); + pass.setVertexBuffer(3, mesh.bitangents.buffer, 0, mesh.bitangents.sizeBytes()); + pass.setVertexBuffer(4, mesh.uvs.buffer, 0, mesh.uvs.sizeBytes()); pass.setIndexBuffer(mesh.indices.buffer, wgpu::IndexFormat::Uint32, 0, mesh.indices.sizeBytes()); static_assert(std::is_same_v); @@ -757,7 +791,7 @@ namespace mrover { depthStencilAttachment.view = mDepthTextureView; wgpu::RenderPassEncoder pass = encoder.beginRenderPass(renderPassDescriptor); - pass.setPipeline(mPbrPipeline); + pass.setPipeline(mPbrEnabled ? mPbrPipeline : mBlinnPhongPipeline); if (!mSceneUniforms.buffer) { mSceneUniforms.init(mDevice); @@ -778,6 +812,7 @@ namespace mrover { entry.buffer = mSceneUniforms.buffer; entry.size = sizeof(SceneUniforms); wgpu::BindGroupDescriptor descriptor; + // we can just use mPbrPipeline here because both PBR and Blinn-Phone use the same bind groups descriptor.layout = mPbrPipeline.getBindGroupLayout(1); descriptor.entryCount = 1; descriptor.entries = &entry; diff --git a/simulator/simulator.sensors.cpp b/simulator/simulator.sensors.cpp index 634532989..fda8e327b 100644 --- a/simulator/simulator.sensors.cpp +++ b/simulator/simulator.sensors.cpp @@ -4,7 +4,7 @@ namespace mrover { auto Simulator::renderCamera(Camera& camera, wgpu::CommandEncoder& encoder, wgpu::RenderPassDescriptor const& passDescriptor) -> void { wgpu::RenderPassEncoder colorPass = encoder.beginRenderPass(passDescriptor); - colorPass.setPipeline(mPbrPipeline); + colorPass.setPipeline(mPbrEnabled ? mPbrPipeline : mBlinnPhongPipeline); if (!camera.sceneUniforms.buffer) camera.sceneUniforms.init(mDevice); camera.sceneUniforms.value.lightColor = {1, 1, 1, 1}; @@ -21,6 +21,7 @@ namespace mrover { entry.buffer = camera.sceneUniforms.buffer; entry.size = sizeof(SceneUniforms); wgpu::BindGroupDescriptor descriptor; + // we can just use mPbrPipeline here because both PBR and Blinn-Phone use the same bind groups descriptor.layout = mPbrPipeline.getBindGroupLayout(1); descriptor.entryCount = 1; descriptor.entries = &entry; diff --git a/urdf/textures/soil_normal.png b/urdf/textures/soil_normal.png new file mode 100644 index 000000000..c74a1b90e --- /dev/null +++ b/urdf/textures/soil_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93f8fdb399ff4c50137fafd804efb0b2589e5ef7b2fffbd153aa4eeed3763118 +size 2605044